Skip to content

Commit

Permalink
Merge pull request #10 from KaririCode-Framework/develop
Browse files Browse the repository at this point in the history
feat(processor-pipeline): implement attribute processing system
  • Loading branch information
walmir-silva authored Oct 25, 2024
2 parents cb916d9 + fdd9dad commit e43ba4c
Show file tree
Hide file tree
Showing 14 changed files with 1,236 additions and 64 deletions.
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
"php": "^8.3",
"kariricode/data-structure": "^1.1",
"kariricode/contract": "^2.7",
"kariricode/property-inspector": "^1.0",
"kariricode/exception": "^1.2"
"kariricode/exception": "^1.2",
"kariricode/property-inspector": "^1.2"
},
"autoload": {
"psr-4": {
Expand Down
13 changes: 7 additions & 6 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions src/Contract/ProcessorConfigBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace KaririCode\ProcessorPipeline\Contract;

use KaririCode\Contract\Processor\Attribute\ProcessableAttribute;

interface ProcessorConfigBuilder
{
/**
* Constrói a configuração dos processadores a partir de um atributo processável.
*
* @param ProcessableAttribute $attribute o atributo que fornece os processadores
*
* @return array a configuração dos processadores
*/
public function build(ProcessableAttribute $attribute): array;
}
129 changes: 129 additions & 0 deletions src/Handler/AttributeHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

declare(strict_types=1);

namespace KaririCode\ProcessorPipeline\Handler;

use KaririCode\Contract\Processor\Attribute\CustomizableMessageAttribute;
use KaririCode\Contract\Processor\Attribute\ProcessableAttribute;
use KaririCode\Contract\Processor\ProcessorBuilder;
use KaririCode\Contract\Processor\ProcessorValidator as ProcessorProcessorContract;
use KaririCode\ProcessorPipeline\Contract\ProcessorConfigBuilder as ProcessorConfigBuilderContract;
use KaririCode\ProcessorPipeline\Processor\ProcessorConfigBuilder;
use KaririCode\ProcessorPipeline\Processor\ProcessorValidator;
use KaririCode\PropertyInspector\Contract\PropertyAttributeHandler;
use KaririCode\PropertyInspector\Contract\PropertyChangeApplier;
use KaririCode\PropertyInspector\Utility\PropertyAccessor;

class AttributeHandler implements PropertyAttributeHandler, PropertyChangeApplier
{
private array $processedPropertyValues = [];
private array $processingResultErrors = [];
private array $processingResultMessages = [];
private array $processorCache = [];

public function __construct(
private readonly string $processorType,
private readonly ProcessorBuilder $builder,
private readonly ProcessorProcessorContract $validator = new ProcessorValidator(),
private readonly ProcessorConfigBuilderContract $configBuilder = new ProcessorConfigBuilder()
) {
}

public function handleAttribute(string $propertyName, object $attribute, mixed $value): mixed
{
if (!$attribute instanceof ProcessableAttribute) {
return null;
}

try {
return $this->processAttribute($propertyName, $attribute, $value);
} catch (\Exception $e) {
$this->processingResultErrors[$propertyName][] = $e->getMessage();

return $value;
}
}

private function processAttribute(string $propertyName, ProcessableAttribute $attribute, mixed $value): mixed
{
$config = $this->configBuilder->build($attribute);
$messages = [];

if ($attribute instanceof CustomizableMessageAttribute) {
foreach ($config as $processorName => &$processorConfig) {
if ($message = $attribute->getMessage($processorName)) {
$processorConfig['customMessage'] = $message;
$messages[$processorName] = $message;
}
}
}

$processedValue = $this->processValue($value, $config);

if ($errors = $this->validateProcessors($config, $messages)) {
$this->processingResultErrors[$propertyName] = $errors;
}

$this->processedPropertyValues[$propertyName] = [
'value' => $processedValue,
'messages' => $messages,
];

$this->processingResultMessages[$propertyName] = $messages;

return $processedValue;
}

private function validateProcessors(array $processorsConfig, array $messages): array
{
$errors = [];
foreach ($processorsConfig as $processorName => $config) {
// Simplify cache key to processor name
if (!isset($this->processorCache[$processorName])) {
$this->processorCache[$processorName] = $this->builder->build(
$this->processorType,
$processorName,
$config
);
}

$processor = $this->processorCache[$processorName];

if ($error = $this->validator->validate($processor, $processorName, $messages)) {
$errors[$processorName] = $error;
}
}

return $errors;
}

private function processValue(mixed $value, array $config): mixed
{
return $this->builder
->buildPipeline($this->processorType, $config)
->process($value);
}

public function applyChanges(object $entity): void
{
foreach ($this->processedPropertyValues as $propertyName => $data) {
(new PropertyAccessor($entity, $propertyName))->setValue($data['value']);
}
}

public function getProcessedPropertyValues(): array
{
return $this->processedPropertyValues;
}

public function getProcessingResultErrors(): array
{
return $this->processingResultErrors;
}

public function getProcessingResultMessages(): array
{
return $this->processingResultMessages;
}
}
82 changes: 27 additions & 55 deletions src/Handler/ProcessorAttributeHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,96 +5,68 @@
namespace KaririCode\ProcessorPipeline\Handler;

use KaririCode\Contract\Processor\ProcessorBuilder;
use KaririCode\Contract\Processor\ValidatableProcessor;
use KaririCode\ProcessorPipeline\Result\ProcessedData;
use KaririCode\ProcessorPipeline\Result\ProcessingError;
use KaririCode\ProcessorPipeline\AttributeHandler;
use KaririCode\ProcessorPipeline\Exception\ProcessorRuntimeException;
use KaririCode\ProcessorPipeline\Result\ProcessingResultCollection;
use KaririCode\PropertyInspector\AttributeHandler;

class ProcessorAttributeHandler extends AttributeHandler
final class ProcessorAttributeHandler extends AttributeHandler
{
protected ProcessingResultCollection $results;
private ProcessingResultCollection $results;

public function __construct(
private readonly string $identifier,
private readonly ProcessorBuilder $builder
string $identifier,
ProcessorBuilder $builder
) {
parent::__construct($identifier, $builder);
$this->results = new ProcessingResultCollection();
}

public function processPropertyValue(string $property, mixed $value): mixed
{
$pipeline = $this->builder->buildPipeline(
$this->identifier,
$this->getPropertyProcessors($property)
);
$processorSpecs = $this->getPropertyProcessors($property);

if (empty($processorSpecs)) {
return $value;
}

try {
$processedValue = $pipeline->process($value);
$this->storeProcessedValue($property, $processedValue);
$pipeline = $this->builder->buildPipeline(
$this->identifier,
$processorSpecs
);

// Verifica se há erros de validação
$this->checkValidationErrors($property, $pipeline);
$processedValue = $pipeline->process($value);
$this->results->setProcessedData($property, $processedValue);

return $processedValue;
} catch (\Exception $e) {
$this->storeProcessingError($property, $e);

return $value;
}
}

protected function checkValidationErrors(string $property, $pipeline): void
{
foreach ($pipeline->getProcessors() as $processor) {
if ($processor instanceof ValidatableProcessor && !$processor->isValid()) {
$this->storeValidationError(
$property,
$processor->getErrorKey(),
$processor->getErrorMessage()
);
}
throw ProcessorRuntimeException::processingFailed($property, $e);
}
}

protected function storeProcessedValue(string $property, mixed $value): void
public function getProcessedPropertyValues(): array
{
$processedData = new ProcessedData($property, $value);
$this->results->addProcessedData($processedData);
return [
'values' => $this->results->getProcessedData(),
'timestamp' => time(),
];
}

protected function storeProcessingError(string $property, \Exception $exception): void
public function getProcessingResultErrors(): array
{
$error = new ProcessingError(
$property,
'processingError',
$exception->getMessage()
);
$this->results->addError($error);
return $this->results->getErrors();
}

protected function storeValidationError(string $property, string $errorKey, string $message): void
public function hasErrors(): bool
{
$error = new ProcessingError($property, $errorKey, $message);
$this->results->addError($error);
return $this->results->hasErrors();
}

public function getProcessingResults(): ProcessingResultCollection
{
return $this->results;
}

public function getProcessedPropertyValues(): array
{
return $this->results->getProcessedDataAsArray();
}

public function getProcessingResultErrors(): array
{
return $this->results->getErrorsAsArray();
}

public function reset(): void
{
$this->results = new ProcessingResultCollection();
Expand Down
55 changes: 55 additions & 0 deletions src/Processor/ProcessorConfigBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace KaririCode\ProcessorPipeline\Processor;

use KaririCode\Contract\Processor\Attribute\ProcessableAttribute;
use KaririCode\ProcessorPipeline\Contract\ProcessorConfigBuilder as ProcessorConfigBuilderContract;

readonly class ProcessorConfigBuilder implements ProcessorConfigBuilderContract
{
public function build(ProcessableAttribute $attribute): array
{
$processors = $attribute->getProcessors();
$processorsConfig = [];

foreach ($processors as $key => $processor) {
if ($this->isSimpleProcessor($processor)) {
$processorsConfig[$processor] = $this->getDefaultProcessorConfig();
} elseif ($this->isConfigurableProcessor($processor)) {
$processorName = $this->determineProcessorName($key, $processor);
$processorsConfig[$processorName] = $this->getProcessorConfig($processor);
}
}

return $processorsConfig;
}

private function isSimpleProcessor(mixed $processor): bool
{
return is_string($processor);
}

private function isConfigurableProcessor(mixed $processor): bool
{
return is_array($processor);
}

private function getDefaultProcessorConfig(): array
{
return [];
}

private function determineProcessorName(string|int $key, array $processor): string
{
$nameNormalizer = new ProcessorNameNormalizer();

return $nameNormalizer->normalize($key, $processor);
}

private function getProcessorConfig(array $processor): array
{
return $processor;
}
}
Loading

0 comments on commit e43ba4c

Please sign in to comment.