{"id":17437114,"url":"https://github.com/kariricode-framework/kariricode-processor-pipeline","last_synced_at":"2026-03-03T19:04:24.412Z","repository":{"id":257825133,"uuid":"872108694","full_name":"KaririCode-Framework/kariricode-processor-pipeline","owner":"KaririCode-Framework","description":"A flexible and extensible processor pipeline component for the KaririCode framework. Enables the creation of modular, configurable processing chains for data transformation, validation, and sanitization tasks","archived":false,"fork":false,"pushed_at":"2024-10-24T18:56:31.000Z","size":84,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2024-10-25T06:02:46.625Z","etag":null,"topics":["framework","kariri-code","php","pipeline","processor","processor-architecture"],"latest_commit_sha":null,"homepage":"https://kariricode.org","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/KaririCode-Framework.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-13T19:53:04.000Z","updated_at":"2024-10-24T18:55:50.000Z","dependencies_parsed_at":null,"dependency_job_id":"d5de7b34-d158-434d-8e85-ead53b3b24a4","html_url":"https://github.com/KaririCode-Framework/kariricode-processor-pipeline","commit_stats":{"total_commits":10,"total_committers":2,"mean_commits":5.0,"dds":0.09999999999999998,"last_synced_commit":"6bc3e747f254c56b5fc0cbdf22b1d3ce1497a7d0"},"previous_names":["kariricode-framework/kariricode-processor-pipeline"],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KaririCode-Framework%2Fkariricode-processor-pipeline","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KaririCode-Framework%2Fkariricode-processor-pipeline/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KaririCode-Framework%2Fkariricode-processor-pipeline/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KaririCode-Framework%2Fkariricode-processor-pipeline/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/KaririCode-Framework","download_url":"https://codeload.github.com/KaririCode-Framework/kariricode-processor-pipeline/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249282480,"owners_count":21243546,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["framework","kariri-code","php","pipeline","processor","processor-architecture"],"created_at":"2024-10-17T11:05:49.656Z","updated_at":"2026-03-03T19:04:24.405Z","avatar_url":"https://github.com/KaririCode-Framework.png","language":"PHP","readme":"# KaririCode ProcessorPipeline\n\n\u003cdiv align=\"center\"\u003e\n\n[![PHP 8.4+](https://img.shields.io/badge/PHP-8.4%2B-777BB4?logo=php\u0026logoColor=white)](https://www.php.net/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-22c55e.svg)](LICENSE)\n[![PHPStan Level 9](https://img.shields.io/badge/PHPStan-Level%209-4F46E5)](https://phpstan.org/)\n[![Tests](https://img.shields.io/badge/Tests-128%20passing-22c55e)](https://kariricode.org)\n[![ARFA](https://img.shields.io/badge/ARFA-1.3-orange)](https://kariricode.org)\n[![KaririCode Framework](https://img.shields.io/badge/KaririCode-Framework-orange)](https://kariricode.org)\n\n**Immutable, composable processor pipelines for the KaririCode Framework —  \ncontext-based registry, flexible spec format, structured error collection, PHP 8.4+.**\n\n[Installation](#installation) · [Quick Start](#quick-start) · [Features](#features) · [Pipeline](#the-pipeline) · [Architecture](#architecture)\n\n\u003c/div\u003e\n\n---\n\n## The Problem\n\nBuilding reusable data-processing chains in PHP typically means either rigid class hierarchies or ad-hoc chains of function calls that are hard to test, configure, and compose:\n\n```php\n// The old way: ad-hoc chain, hard to test or reuse\nfunction processInput(string $input): string\n{\n    $input = trim($input);\n    $input = strtolower($input);\n    if (strlen($input) \u003c 3) {\n        throw new \\InvalidArgumentException('Too short');\n    }\n    return $input;\n}\n```\n\nNo registry, no configuration per processor, no error collection, no immutability — just imperative code you copy-paste everywhere.\n\n## The Solution\n\n```php\nuse KaririCode\\ProcessorPipeline\\ProcessorRegistry;\nuse KaririCode\\ProcessorPipeline\\ProcessorBuilder;\n\n// 1. Register processors once, per context\n$registry = new ProcessorRegistry();\n$registry\n    -\u003eregister('sanitizer', 'trim',      new TrimProcessor())\n    -\u003eregister('sanitizer', 'lowercase', new LowercaseProcessor())\n    -\u003eregister('validator', 'length',    new LengthValidator());\n\n// 2. Build immutable pipelines from specs\n$builder   = new ProcessorBuilder($registry);\n$sanitized = $builder-\u003ebuildPipeline('sanitizer', ['trim', 'lowercase']);\n$validated = $builder-\u003ebuildPipeline('validator', [\n    'length' =\u003e ['minLength' =\u003e 3, 'maxLength' =\u003e 50],\n]);\n\n// 3. Execute — pipelines are immutable and reusable\n$output = $sanitized-\u003eprocess('  HELLO WORLD  '); // 'hello world'\n$validated-\u003eprocess($output);\n```\n\n---\n\n## Requirements\n\n| Requirement | Version |\n|---|---|\n| PHP | 8.4 or higher |\n| kariricode/contract | ^2.8 |\n| kariricode/exception | ^1.2 |\n\n---\n\n## Installation\n\n```bash\ncomposer require kariricode/processor-pipeline\n```\n\n---\n\n## Quick Start\n\nDefine processors, register them, build a pipeline, execute:\n\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\nrequire_once __DIR__ . '/vendor/autoload.php';\n\nuse KaririCode\\Contract\\Processor\\Processor;\nuse KaririCode\\Contract\\Processor\\ConfigurableProcessor;\nuse KaririCode\\ProcessorPipeline\\ProcessorRegistry;\nuse KaririCode\\ProcessorPipeline\\ProcessorBuilder;\n\n// 1. Define processors\nfinal class TrimProcessor implements Processor\n{\n    public function process(mixed $input): mixed\n    {\n        return is_string($input) ? trim($input) : $input;\n    }\n}\n\nfinal class LengthValidator implements ConfigurableProcessor\n{\n    private int $min = 0;\n    private int $max = PHP_INT_MAX;\n\n    public function configure(array $options): void\n    {\n        $this-\u003emin = $options['minLength'] ?? $this-\u003emin;\n        $this-\u003emax = $options['maxLength'] ?? $this-\u003emax;\n    }\n\n    public function process(mixed $input): mixed\n    {\n        $len = mb_strlen((string) $input);\n        if ($len \u003c $this-\u003emin || $len \u003e $this-\u003emax) {\n            throw new \\LengthException(\"Length must be between {$this-\u003emin} and {$this-\u003emax}.\");\n        }\n        return $input;\n    }\n}\n\n// 2. Register and build\n$registry = new ProcessorRegistry();\n$registry\n    -\u003eregister('sanitizer', 'trim',   new TrimProcessor())\n    -\u003eregister('validator', 'length', new LengthValidator());\n\n$builder  = new ProcessorBuilder($registry);\n$pipeline = $builder-\u003ebuildPipeline('sanitizer', ['trim']);\n$validate = $builder-\u003ebuildPipeline('validator', [\n    'length' =\u003e ['minLength' =\u003e 3, 'maxLength' =\u003e 50],\n]);\n\n// 3. Execute\n$sanitized = $pipeline-\u003eprocess('  Hello, World!  '); // 'Hello, World!'\n$validate-\u003eprocess($sanitized);                        // passes — 13 chars\n\nvar_dump($sanitized); // string(13) \"Hello, World!\"\n```\n\n---\n\n## Features\n\n### Immutable Pipelines (ARFA P1)\n\n`Pipeline` is a `readonly class`. Adding processors returns a **new** instance — the original is never modified:\n\n```php\n$base     = $builder-\u003ebuildPipeline('sanitizer', ['trim']);\n$extended = $base-\u003ewithProcessor(new LowercaseProcessor());\n\n// $base still has 1 processor\n// $extended has 2 processors\nassert($base-\u003ecount() === 1);\nassert($extended-\u003ecount() === 2);\n```\n\n### Context-Based Registry\n\nProcessors are namespaced by context — no name collisions across domains:\n\n```php\n$registry-\u003eregister('validator', 'email', new EmailValidator());\n$registry-\u003eregister('sanitizer', 'email', new EmailSanitizer()); // same name, different context\n\n$validationPipeline  = $builder-\u003ebuildPipeline('validator', ['email']);\n$sanitizationPipeline = $builder-\u003ebuildPipeline('sanitizer', ['email']);\n```\n\n### Flexible Specification Format\n\nBuild pipelines with simple lists, enable/disable flags, or per-processor configuration:\n\n```php\n$pipeline = $builder-\u003ebuildPipeline('validator', [\n    'required',                                  // Simple: always enabled\n    'trim'   =\u003e true,                            // Explicit: enabled\n    'strict' =\u003e false,                           // Explicit: disabled (skipped)\n    'length' =\u003e ['minLength' =\u003e 3, 'maxLength' =\u003e 50], // Configured\n]);\n```\n\n### Configurable Processors\n\n`ConfigurableProcessor` allows per-build configuration without constructing new instances:\n\n```php\nfinal class SlugProcessor implements ConfigurableProcessor\n{\n    private string $separator = '-';\n\n    public function configure(array $options): void\n    {\n        $this-\u003eseparator = $options['separator'] ?? '-';\n    }\n\n    public function process(mixed $input): mixed\n    {\n        return strtolower(str_replace(' ', $this-\u003eseparator, trim((string) $input)));\n    }\n}\n\n$pipeline = $builder-\u003ebuildPipeline('formatter', [\n    'slug' =\u003e ['separator' =\u003e '_'],\n]);\n$pipeline-\u003eprocess('Hello World'); // 'hello_world'\n```\n\n### Error Collection via ProcessorHandler\n\nWrap processors for non-halting error collection — useful in validation scenarios:\n\n```php\nuse KaririCode\\ProcessorPipeline\\Handler\\ProcessorHandler;\nuse KaririCode\\ProcessorPipeline\\Result\\ProcessingResultCollection;\n\n$results = new ProcessingResultCollection();\n\n$handler = new ProcessorHandler(\n    processor:        new EmailValidator(),\n    resultCollection: $results,\n    haltOnError:      false,   // continue on failure\n);\n\n$output = $handler-\u003eprocess('not-an-email'); // returns input unchanged\n\nif ($results-\u003ehasErrors()) {\n    foreach ($results-\u003egetErrors() as $processor =\u003e $errors) {\n        foreach ($errors as $error) {\n            echo \"{$processor}: {$error['message']}\\n\";\n        }\n    }\n}\n```\n\n### PHP 8.4 Attributes\n\nUse `#[Process]` to declare pipelines declaratively on entity properties:\n\n```php\nuse KaririCode\\ProcessorPipeline\\Attribute\\Process;\n\nfinal class UserProfile\n{\n    #[Process(\n        processors: ['trim', 'lowercase'],\n        messages:   [],\n    )]\n    public private(set) string $email = '';\n\n    #[Process(\n        processors: ['required', 'length' =\u003e ['minLength' =\u003e 3]],\n        messages:   ['required' =\u003e 'Username is required.'],\n    )]\n    public private(set) string $username = '';\n}\n```\n\n### Structured Exceptions\n\nAll exceptions carry a `context` array for structured logging and tracing:\n\n```php\nuse KaririCode\\ProcessorPipeline\\Exception\\PipelineExecutionException;\n\ntry {\n    $pipeline-\u003eprocess($input);\n} catch (PipelineExecutionException $e) {\n    // $e-\u003econtext['stage']         — stage index where failure occurred\n    // $e-\u003econtext['processorName'] — FQCN of the failing processor\n    // $e-\u003egetPrevious()            — original exception\n}\n```\n\n---\n\n## The Pipeline\n\n```\nProcessorBuilder::buildPipeline($context, $specs)\n        │\n        ▼\nforeach spec entry:\n    resolveSpec($key, $value)\n      ├── string key   → name only (enabled, default config)\n      ├── name =\u003e true → enabled, no config\n      ├── name =\u003e false → SKIP\n      └── name =\u003e [..] → enabled + configure()\n        │\n        ▼\n    ProcessorRegistry::get($context, $name)\n    ConfigurableProcessor::configure($options)  ← if applicable\n    $processors[] = $processor\n        │\n        ▼\nnew Pipeline($processors)   ← immutable, readonly\n\nPipeline::process($input)\n        │\n        ▼\nforeach processor as $index =\u003e $p:\n    try:\n        $state = $p-\u003eprocess($state)\n    catch \\Throwable:\n        throw PipelineExecutionException::atStage($p::class, $index, $cause)\n        │\n        ▼\nreturn $state\n```\n\n---\n\n## Architecture\n\n### Source layout\n\n```\nsrc/\n├── Attribute/\n│   └── Process.php                       PHP 8.4 attribute for declarative pipelines\n├── Exception/\n│   ├── PipelineExecutionException.php    Stage-aware failure with context array\n│   ├── ProcessorNotFoundException.php    Registry miss\n│   ├── InvalidProcessorConfigurationException.php\n│   └── ProcessorPipelineException.php    Base exception\n├── Handler/\n│   └── ProcessorHandler.php             Error-collecting processor wrapper\n├── Pipeline/\n│   └── Pipeline.php                     Immutable readonly sequential executor\n├── Result/\n│   └── ProcessingResultCollection.php   Error + execution trace accumulator\n├── ProcessorBuilder.php                 Factory: spec → Pipeline\n└── ProcessorRegistry.php               Context-based processor store\n```\n\n### Key design decisions\n\n| Decision | Rationale | ADR |\n|---|---|---|\n| Immutable `readonly` Pipeline | Eliminates shared-state bugs; safe to reuse across requests | [ADR-001](docs/adrs/ADR-001-immutable-pipeline.md) |\n| Context-based registry | Prevents name collisions between validator/sanitizer/transformer domains | [ADR-002](docs/adrs/ADR-002-context-based-registry.md) |\n| Flexible spec format | Same interface for simple lists and richly-configured pipelines | [ADR-003](docs/adrs/ADR-003-processor-specification-format.md) |\n| `ProcessorHandler` wrapper | Decouples error collection from processor logic; supports halt-or-continue | — |\n| `PipelineExecutionException` with stage context | Structured observability — which stage, which processor, which cause | — |\n\n### Specifications\n\n| Spec | Covers |\n|---|---|\n| [SPEC-001](docs/specs/SPEC-001-processor-pipeline.md) | Full pipeline: registry → builder → execution → error collection |\n\n---\n\n## Integration with the KaririCode Ecosystem\n\nProcessorPipeline is the **execution engine** used internally by other KaririCode components:\n\n| Component | Role |\n|---|---|\n| `kariricode/validator` | Builds validation pipelines from `#[Validate]` attributes |\n| `kariricode/sanitizer` | Builds sanitization pipelines from `#[Sanitize]` attributes |\n| `kariricode/transformer` | Builds transformation pipelines from `#[Transform]` attributes |\n| `kariricode/property-inspector` | Discovers `#[Process]` attributes and dispatches to pipeline handlers |\n\nAny component that needs **configurable, composable processing chains** can be built on top of this engine.\n\n---\n\n## Project Stats\n\n| Metric | Value |\n|---|---|\n| PHP source files | 8 |\n| External runtime dependencies | 2 (contract · exception) |\n| Test suite | 128 tests · 234 assertions |\n| PHPStan level | 9 |\n| Code coverage | 100% classes / methods / lines |\n| PHP version | 8.4+ |\n| ARFA compliance | 1.3 |\n| Test suites | Unit + Integration |\n\n---\n\n## Contributing\n\n```bash\ngit clone https://github.com/KaririCode-Framework/kariricode-processor-pipeline.git\ncd kariricode-processor-pipeline\ncomposer install\nkcode init\nkcode quality  # Must pass before opening a PR\n```\n\n---\n\n## License\n\n[MIT License](LICENSE) © [Walmir Silva](mailto:community@kariricode.org)\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\nPart of the **[KaririCode Framework](https://kariricode.org)** ecosystem.\n\n[kariricode.org](https://kariricode.org) · [GitHub](https://github.com/KaririCode-Framework/kariricode-processor-pipeline) · [Packagist](https://packagist.org/packages/kariricode/processor-pipeline) · [Issues](https://github.com/KaririCode-Framework/kariricode-processor-pipeline/issues)\n\n\u003c/div\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkariricode-framework%2Fkariricode-processor-pipeline","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkariricode-framework%2Fkariricode-processor-pipeline","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkariricode-framework%2Fkariricode-processor-pipeline/lists"}