Skip to content

Commit 12a3972

Browse files
authored
Release/2.1.0 (#42)
* feat: Add lazy collection creation from closure and enhance collection methods. * refactor: Inline private toGenerator method in Equality * refactor: Make EagerPipeline truly eager and final readonly * refactor: Inline private pipeTo method in Collection * refactor: Remove PHPDoc from Order enum * docs: Add missing badges to README * fix: Correct indentation in EagerPipeline * fix: Correct indentation in Equality * fix: Correct indentation in Collection * fix: Correct indentation in Order * fix: Remove duplicate class in EagerPipeline * fix: Break long line in sort method to comply with 120-char limit * fix: Simplify EagerPipeline to fix phpstan analysis * docs: Update README with createLazyFromClosure and each void return * fix: Refactor Filter and Rearrange classes for improved readability.
1 parent 7ab5f0f commit 12a3972

19 files changed

Lines changed: 849 additions & 116 deletions

.claude/CLAUDE.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Project
2+
3+
PHP microservices platform. Hexagonal architecture (ports & adapters), DDD, CQRS.
4+
5+
## Rules
6+
7+
All coding standards, architecture, naming, testing, documentation, and OpenAPI conventions
8+
are defined in `rules/`. Read the applicable rule files before generating any code or documentation.
9+
10+
## Commands
11+
12+
- `make test` — run tests with coverage.
13+
- `make mutation-test` — run mutation testing (Infection).
14+
- `make review` — run lint.
15+
- `make help` — list all available commands.
16+
17+
## Post-change validation
18+
19+
After any code change, run `make review`, `make test`, and `make mutation-test`.
20+
If any fails, iterate on the fix while respecting all project rules until all pass.
21+
Never deliver code that breaks lint, tests, or leaves surviving mutants.
22+
23+
## File formatting
24+
25+
Every file produced or modified must:
26+
27+
- Use **LF** line endings. Never CRLF.
28+
- Have no trailing whitespace on any line.
29+
- End with a single trailing newline.
30+
- Have no consecutive blank lines (max one blank line between blocks).

.claude/rules/documentation.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
description: Standards for README files and all project documentation in PHP libraries.
3+
paths:
4+
- "**/*.md"
5+
---
6+
7+
# Documentation
8+
9+
## README
10+
11+
1. Include an anchor-linked table of contents.
12+
2. Start with a concise one-line description of what the library does.
13+
3. Include a **badges** section (license, build status, coverage, latest version, PHP version).
14+
4. Provide an **Overview** section explaining the problem the library solves and its design philosophy.
15+
5. **Installation** section: Composer command (`composer require vendor/package`).
16+
6. **How to use** section: complete, runnable code examples covering the primary use cases. Each example
17+
includes a brief heading describing what it demonstrates.
18+
7. If the library exposes multiple entry points, strategies, or container types, document each with its own
19+
subsection and example.
20+
8. **FAQ** section: include entries for common pitfalls, non-obvious behaviors, or design decisions that users
21+
frequently ask about. Each entry is a numbered question as heading (e.g., `### 01. Why does X happen?`)
22+
followed by a concise explanation. Only include entries that address real confusion points.
23+
9. **License** and **Contributing** sections at the end.
24+
10. Write strictly in American English. See `rules/code-style.md` American English section for spelling conventions.
25+
26+
## Structured data
27+
28+
1. When documenting constructors, factory methods, or configuration options with more than 3 parameters,
29+
use tables with columns: Parameter, Type, Required, Description.
30+
2. Prefer tables to prose for any structured information.
31+
32+
## Style
33+
34+
1. Keep language concise and scannable.
35+
2. Never include placeholder content (`TODO`, `TBD`).
36+
3. Code examples must be syntactically correct and self-contained.
37+
4. Do not document `Internal/` classes or private API. Only document what consumers interact with.

.claude/rules/github-workflows.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
---
2+
description: Naming, ordering, inputs, security, and structural rules for all GitHub Actions workflow files.
3+
paths:
4+
- ".github/workflows/**/*.yml"
5+
- ".github/workflows/**/*.yaml"
6+
---
7+
8+
# Workflows
9+
10+
Structural and stylistic rules for GitHub Actions workflow files. Refer to `shell-scripts.md` for Bash conventions used
11+
inside `run:` steps, and to `terraforms.md` for Terraform conventions used in `terraform/`.
12+
13+
## Pre-output checklist
14+
15+
Verify every item before producing any workflow YAML. If any item fails, revise before outputting.
16+
17+
1. File name follows the convention: `ci-<runtime>.yml` for reusable CI, `cd-<purpose>.yml` for dispatch CD.
18+
2. `name` field follows the pattern `CI — <Context>` or `CD — <Context>`, using sentence case after the dash
19+
(e.g., `CD — Run migration`, not `CD — Run Migration`).
20+
3. Reusable workflows use `workflow_call` trigger. CD workflows use `workflow_dispatch` trigger.
21+
4. Each workflow has a single responsibility. CI tests code. CD deploys it. Never combine both.
22+
5. Every input has a `description` field. Descriptions use American English and end with a period.
23+
6. Input names use `kebab-case`: `service-name`, `dry-run`, `skip-build`.
24+
7. Inputs are ordered: required first, then optional. Each group by **name length ascending**.
25+
8. Choice input options are in **alphabetical order**.
26+
9. `env`, `outputs`, and `with` entries are ordered by **key length ascending**.
27+
10. `permissions` keys are ordered by **key length ascending** (`contents` before `id-token`).
28+
11. Top-level workflow keys follow canonical order: `name`, `on`, `concurrency`, `permissions`, `env`, `jobs`.
29+
12. Job-level properties follow canonical order: `if`, `name`, `needs`, `uses`, `with`, `runs-on`,
30+
`environment`, `timeout-minutes`, `strategy`, `outputs`, `permissions`, `env`, `steps`.
31+
13. All other YAML property names within a block are ordered by **name length ascending**.
32+
14. Jobs follow execution order: `load-config``lint``test``build``deploy`.
33+
15. Step names start with a verb and use sentence case: `Setup PHP`, `Run lint`, `Resolve image tag`.
34+
16. Runtime versions are resolved from the service repo's native dependency file (`composer.json`, `go.mod`,
35+
`package.json`). No version is hardcoded in any workflow.
36+
17. Service-specific overrides live in a pipeline config file (e.g., `.pipeline.yml`) in the service repo,
37+
not in the workflows repository.
38+
18. The `load-config` job reads the pipeline config file at runtime with safe fallback to defaults when absent.
39+
19. Top-level `permissions` defaults to read-only (`contents: read`). Jobs escalate only the permissions they
40+
need.
41+
20. AWS authentication uses OIDC federation exclusively. Static access keys are forbidden.
42+
21. Secrets are passed via `secrets: inherit` from callers. No secret is hardcoded.
43+
22. Sensitive values fetched from SSM are masked with `::add-mask::` before assignment.
44+
23. Third-party actions are pinned to the latest available full commit SHA with a version comment:
45+
`uses: aws-actions/configure-aws-credentials@<latest-sha> # v4.0.2`. Always verify the latest
46+
version before generating a workflow.
47+
24. First-party actions (`actions/*`) are pinned to the latest major version tag available:
48+
`actions/checkout@v4`. Always check for the most recent major version before generating a workflow.
49+
25. Production deployments require GitHub Environments protection rules (manual approval).
50+
26. Every job sets `timeout-minutes` to prevent indefinite hangs. CI jobs: 10–15 minutes. CD jobs: 20–30
51+
minutes. Adjust only with justification in a comment.
52+
27. CI workflows set `concurrency` with `group` scoped to the PR and `cancel-in-progress: true` to avoid
53+
redundant runs.
54+
28. CD workflows set `concurrency` with `group` scoped to the environment and `cancel-in-progress: false` to
55+
prevent interrupted deployments.
56+
29. CD workflows use `if: ${{ !cancelled() }}` to allow to deploy after optional build steps.
57+
30. Inline logic longer than 3 lines is extracted to a script in `scripts/ci/` or `scripts/cd/`.
58+
59+
## Style
60+
61+
- All text (workflow names, step names, input descriptions, comments) uses American English with correct
62+
spelling and punctuation. Sentences and descriptions end with a period.
63+
64+
## Callers
65+
66+
- Callers trigger on `pull_request` targeting `main` only. No `push` trigger.
67+
- Callers in service repos are static (~10 lines) and pass only `service-name` or `app-name`.
68+
- Callers reference workflows with `@main` during development. Pin to a tag or SHA for production.
69+
70+
## Image tagging
71+
72+
- CD deploy builds: `<environment>-sha-<short-hash>` + `latest`.
73+
74+
## Migrations
75+
76+
- Migrations run **before** service deployment (schema first, code second).
77+
- `cd-migrate.yml` supports `dry-run` mode (`flyway validate`) for pre-flight checks.
78+
- Database credentials are fetched from SSM at runtime, never stored in workflow files.

.claude/rules/php-code-style.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
---
2+
description: Pre-output checklist, naming, typing, comparisons, and PHPDoc rules for all PHP files in libraries.
3+
paths:
4+
- "src/**/*.php"
5+
- "tests/**/*.php"
6+
---
7+
8+
# Code style
9+
10+
Semantic code rules for all PHP files. Formatting rules (PSR-1, PSR-4, PSR-12, line length) are enforced by `phpcs.xml`
11+
and are not repeated here. Refer to `rules/domain.md` for domain modeling rules.
12+
13+
## Pre-output checklist
14+
15+
Verify every item before producing any PHP code. If any item fails, revise before outputting.
16+
17+
1. `declare(strict_types=1)` is present.
18+
2. All classes are `final readonly` by default. Use `class` (without `final` or `readonly`) only when the class is
19+
designed as an extension point for consumers (e.g., `Collection`, `ValueObject`). Use `final class` without
20+
`readonly` only when the parent class is not readonly (e.g., extending a third-party abstract class).
21+
3. All parameters, return types, and properties have explicit types.
22+
4. Constructor property promotion is used.
23+
5. Named arguments are used at call sites for own code, tests, and third-party library methods (e.g., tiny-blocks).
24+
Never use named arguments on native PHP functions (`array_map`, `in_array`, `preg_match`, `is_null`,
25+
`iterator_to_array`, `sprintf`, `implode`, etc.) or PHPUnit assertions (`assertEquals`, `assertSame`,
26+
`assertTrue`, `expectException`, etc.).
27+
6. No `else` or `else if` exists anywhere. Use early returns, polymorphism, or map dispatch instead.
28+
7. No abbreviations appear in identifiers. Use `$index` instead of `$i`, `$account` instead of `$acc`.
29+
8. No generic identifiers exist. Use domain-specific names instead:
30+
`$data``$payload`, `$value``$totalAmount`, `$item``$element`,
31+
`$info``$currencyDetails`, `$result``$conversionOutcome`.
32+
9. No raw arrays exist where a typed collection or value object is available. Use `tiny-blocks/collection`
33+
(`Collection`, `Collectible`) instead of raw `array` for any list of domain objects. Raw arrays are acceptable
34+
only for primitive configuration data, variadic pass-through, or interop at system boundaries.
35+
10. No private methods exist except private constructors for factory patterns. Inline trivial logic at the call site
36+
or extract it to a collaborator or value object.
37+
11. Members are ordered: constants first, then constructor, then static methods, then instance methods. Within each
38+
group, order by body size ascending (number of lines between `{` and `}`). Constants and enum cases, which have
39+
no body, are ordered by name length ascending.
40+
12. Constructor parameters are ordered by parameter name length ascending (count the name only, without `$` or type),
41+
except when parameters have an implicit semantic order (e.g., `$start/$end`, `$from/$to`, `$startAt/$endAt`),
42+
which takes precedence. The same rule applies to named arguments at call sites.
43+
Example: `$id` (2) → `$value` (5) → `$status` (6) → `$precision` (9).
44+
13. No O(N²) or worse complexity exists.
45+
14. No logic is duplicated across two or more places (DRY).
46+
15. No abstraction exists without real duplication or isolation need (KISS).
47+
16. All identifiers, comments, and documentation are written in American English.
48+
17. No justification comments exist (`// NOTE:`, `// REASON:`, etc.). Code speaks for itself.
49+
18. `// TODO: <reason>` is used when implementation is unknown, uncertain, or intentionally deferred.
50+
Never leave silent gaps.
51+
19. All class references use `use` imports at the top of the file. Fully qualified names inline are prohibited.
52+
20. No dead or unused code exists. Remove unreferenced classes, methods, constants, and imports.
53+
21. Never create public methods, constants, or classes in `src/` solely to serve tests. If production code does not
54+
need it, it does not exist.
55+
22. Always use the most current and clean syntax available in the target PHP version. Prefer match to switch,
56+
first-class callables over `Closure::fromCallable()`, readonly promotion over manual assignment, enum methods
57+
over external switch/if chains, named arguments over positional ambiguity (except where excluded by rule 5),
58+
and `Collection::map` over foreach accumulation.
59+
23. No vertical alignment of types in parameter lists or property declarations. Use a single space between
60+
type and variable name. Never pad with extra spaces to align columns:
61+
`public OrderId $id` — not `public OrderId $id`.
62+
24. Opening brace `{` goes on the same line as the closing parenthesis `)` for constructors, methods, and
63+
closures: `): ReturnType {` — not `): ReturnType\n {`. Parameters with default values go last.
64+
65+
## Casing conventions
66+
67+
- Internal code (variables, methods, classes): **`camelCase`**.
68+
- Constants and enum-backed values when representing codes: **`SCREAMING_SNAKE_CASE`**.
69+
70+
## Naming
71+
72+
- Names describe **what** in domain terms, not **how** technically: `$monthlyRevenue` instead of `$calculatedValue`.
73+
- Generic technical verbs (`process`, `handle`, `execute`, `mark`, `enforce`, `manage`, `ensure`, `validate`,
74+
`check`, `verify`, `assert`, `transform`, `parse`, `compute`, `sanitize`, `normalize`) **should be avoided**.
75+
Prefer names that describe the domain operation.
76+
- Booleans use predicate form: `isActive`, `hasPermission`, `wasProcessed`.
77+
- Collections are always plural: `$orders`, `$lines`.
78+
- Methods returning bool use prefixes: `is`, `has`, `can`, `was`, `should`.
79+
80+
## Comparisons
81+
82+
1. Null checks: use `is_null($variable)`, never `$variable === null`.
83+
2. Empty string checks on typed `string` parameters: use `$variable === ''`. Avoid `empty()` on typed strings
84+
because `empty('0')` returns `true`.
85+
3. Mixed or untyped checks (value may be `null`, empty string, `0`, or `false`): use `empty($variable)`.
86+
87+
## American English
88+
89+
All identifiers, enum values, comments, and error codes use American English spelling:
90+
`canceled` (not `cancelled`), `organization` (not `organisation`), `initialize` (not `initialise`),
91+
`behavior` (not `behaviour`), `modeling` (not `modelling`), `labeled` (not `labelled`),
92+
`fulfill` (not `fulfil`), `color` (not `colour`).
93+
94+
## PHPDoc
95+
96+
- PHPDoc is restricted to interfaces only, documenting obligations and `@throws`.
97+
- Never add PHPDoc to concrete classes.
98+
99+
## Collection usage
100+
101+
When a property or parameter is `Collectible`, use its fluent API. Never break out to raw array functions.
102+
103+
**Prohibited — `array_map` + `iterator_to_array` on a Collectible:**
104+
105+
```php
106+
$names = array_map(
107+
static fn(Element $element): string => $element->name(),
108+
iterator_to_array($collection)
109+
);
110+
```
111+
112+
**Correct — fluent chain with `map()` + `toArray()`:**
113+
114+
```php
115+
$names = $collection
116+
->map(transformations: static fn(Element $element): string => $element->name())
117+
->toArray(keyPreservation: KeyPreservation::DISCARD);
118+
```
119+
120+
The same applies to `filter()`, `reduce()`, `each()`, and all other `Collectible` operations. Chain them
121+
fluently. Never materialize with `iterator_to_array` to then pass into a raw `array_*` function.

.claude/rules/php-domain.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
---
2+
description: Domain modeling rules for PHP libraries — folder structure, naming, value objects, exceptions, enums, and SOLID.
3+
paths:
4+
- "src/**/*.php"
5+
---
6+
7+
# Domain modeling
8+
9+
Libraries are self-contained packages. The core has no dependency on frameworks, databases, or I/O.
10+
Refer to `rules/code-style.md` for the pre-output checklist applied to all PHP code.
11+
12+
## Folder structure
13+
14+
```
15+
src/
16+
├── <PublicInterface>.php # Primary contract for consumers
17+
├── <Implementation>.php # Main implementation or extension point
18+
├── <Enum>.php # Public enum
19+
├── Contracts/ # Interfaces for data returned to consumers
20+
├── Internal/ # Implementation details (not part of public API)
21+
│ ├── <Collaborator>.php
22+
│ └── Exceptions/ # Internal exception classes
23+
├── <Feature>/ # Feature-specific subdirectory when needed
24+
└── Exceptions/ # Public exception classes (when part of the API)
25+
```
26+
27+
**Public API boundary:** Only interfaces, extension points, enums, and thin orchestration classes live at the
28+
`src/` root. These classes define the contract consumers interact with and delegate all real work to collaborators
29+
inside `src/Internal/`. If a class contains substantial logic (algorithms, state machines, I/O), it belongs in
30+
`Internal/`, not at the root.
31+
32+
The `Internal/` namespace signals classes that are implementation details. Consumers must not depend on them.
33+
Never use `Entities/`, `ValueObjects/`, `Enums/`, or `Domain/` as folder names.
34+
35+
## Nomenclature
36+
37+
1. Every class, property, method, and exception name reflects the **domain concept** the library represents.
38+
A math library uses `Precision`, `RoundingMode`; a money library uses `Currency`, `Amount`; a collection
39+
library uses `Collectible`, `Order`.
40+
2. Never use generic technical names: `Manager`, `Helper`, `Processor`, `Data`, `Info`, `Utils`,
41+
`Item`, `Record`, `Entity`, `Exception`, `Ensure`, `Validate`, `Check`, `Verify`,
42+
`Assert`, `Transform`, `Parse`, `Compute`, `Sanitize`, or `Normalize` as class suffixes or prefixes.
43+
3. Name classes after what they represent: `Money`, `Color`, `Pipeline` — not after what they do technically.
44+
4. Name methods after the operation in domain terms: `add()`, `convertTo()`, `splitAt()` — not `process()`,
45+
`handle()`, `execute()`, `manage()`, `ensure()`, `validate()`, `check()`, `verify()`, `assert()`,
46+
`transform()`, `parse()`, `compute()`, `sanitize()`, or `normalize()`.
47+
48+
## Value objects
49+
50+
1. Are immutable: no setters, no mutation after construction. Operations return new instances.
51+
2. Compare by value, not by reference.
52+
3. Validate invariants in the constructor and throw on invalid input.
53+
4. Have no identity field.
54+
5. Use static factory methods (e.g., `from`, `of`, `zero`) with a private constructor when multiple creation
55+
paths exist.
56+
57+
## Exceptions
58+
59+
1. Extend native PHP exceptions (`DomainException`, `InvalidArgumentException`, `OverflowException`, etc.).
60+
2. Are pure: no formatted `code`/`message` for HTTP responses.
61+
3. Signal invariant violations only.
62+
4. Name after the invariant violated, never after the technical type:
63+
`PrecisionOutOfRange` — not `InvalidPrecisionException`.
64+
`CurrencyMismatch` — not `BadCurrencyException`.
65+
`ContainerWaitTimeout` — not `TimeoutException`.
66+
5. Create the exception class directly with the invariant name and the appropriate native parent. The exception
67+
is dedicated by definition when its name describes the specific invariant it guards.
68+
69+
## Enums
70+
71+
1. Are PHP backed enums.
72+
2. Include domain-meaningful methods when needed (e.g., `Order::ASCENDING_KEY`).
73+
74+
## Extension points
75+
76+
1. When a class is designed to be extended by consumers (e.g., `Collection`, `ValueObject`), it uses `class`
77+
instead of `final readonly class`. All other classes use `final readonly class`.
78+
2. Extension point classes use a private constructor with static factory methods (`createFrom`, `createFromEmpty`)
79+
as the only creation path.
80+
3. Internal state is injected via the constructor and stored in a `private readonly` property.
81+
82+
## Principles
83+
84+
- **Immutability**: all models and value objects adopt immutability. Operations return new instances.
85+
- **Zero dependencies**: the library's core has no dependency on frameworks, databases, or I/O.
86+
- **Small surface area**: expose only what consumers need. Hide implementation in `Internal/`.
87+
88+
## SOLID reference
89+
90+
| Principle | Failure signal |
91+
|---------------------------|---------------------------------------------|
92+
| S — Single responsibility | Class does two unrelated things |
93+
| O — Open/closed | Adding a feature requires editing internals |
94+
| L — Liskov substitution | Subclass throws on parent method |
95+
| I — Interface segregation | Interface has unused methods |
96+
| D — Dependency inversion | Constructor uses `new ConcreteClass()` |

0 commit comments

Comments
 (0)