Skip to content

Commit 0c0f2e4

Browse files
authored
Release/2.1.0 (#17)
1 parent b6e0cd1 commit 0c0f2e4

31 files changed

Lines changed: 1237 additions & 615 deletions

.claude/CLAUDE.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Project
2+
3+
PHP library (tiny-blocks). Immutable domain models, zero infrastructure dependencies in core.
4+
5+
## Stack
6+
7+
Refer to `composer.json` for the full dependency list, version constraints, and PHP version.
8+
9+
## Project layout
10+
11+
```
12+
src/
13+
├── <PublicInterface>.php # Primary contract for consumers
14+
├── <Implementation>.php # Main implementation or extension point
15+
├── Contracts/ # Interfaces for data returned to consumers
16+
├── Internal/ # Implementation details (not part of public API)
17+
│ └── Exceptions/ # Internal exception classes
18+
└── Exceptions/ # Public exception classes (when part of the API)
19+
tests/
20+
├── Models/ # Domain-specific fixtures reused across tests
21+
├── Mocks/ # Test doubles for system boundaries
22+
├── Unit/ # Unit tests for public API
23+
└── Integration/ # Tests requiring real external resources (when applicable)
24+
```
25+
26+
See `rules/domain.md` for folder conventions and naming rules.
27+
28+
## Commands
29+
30+
- Run tests: `make test`.
31+
- Run lint: `make review`.
32+
- Run `make help` to list all available commands.
33+
34+
## Post-change validation
35+
36+
After any code change, run `make review` and `make test`.
37+
If either fails, iterate on the fix while respecting all project rules until both pass.
38+
Never deliver code that breaks lint or tests.
39+
40+
## Reference-first approach
41+
42+
Always read all rule files and reference sources before generating any code or documentation.
43+
Never generate from memory. Read the source and match the pattern exactly.

.claude/rules/code-style.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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.).
26+
6. No `else` or `else if` exists anywhere. Use early returns, polymorphism, or map dispatch instead.
27+
7. No abbreviations appear in identifiers. Use `$index` instead of `$i`, `$account` instead of `$acc`.
28+
8. No generic identifiers exist. Use domain-specific names instead:
29+
`$data``$payload`, `$value``$totalAmount`, `$item``$element`,
30+
`$info``$details`, `$result``$outcome`.
31+
9. No raw arrays exist where a typed collection or value object is available. Use `tiny-blocks/collection`
32+
(`Collection`, `Collectible`) instead of raw `array` for any list of domain objects. Raw arrays are acceptable
33+
only for primitive configuration data, variadic pass-through, or interop at system boundaries.
34+
10. No private methods exist except private constructors for factory patterns. Inline trivial logic at the call site
35+
or extract it to a collaborator or value object.
36+
11. Members are ordered: constants first, then constructor, then static methods, then instance methods. Within each
37+
group, order by body size ascending (number of lines between `{` and `}`). Constants and enum cases, which have
38+
no body, are ordered by name length ascending.
39+
12. Constructor parameters are ordered by parameter name length ascending (count the name only, without `$` or type),
40+
except when parameters have an implicit semantic order (e.g., `$start/$end`, `$from/$to`, `$startAt/$endAt`),
41+
which takes precedence. The same rule applies to named arguments at call sites.
42+
Example: `$id` (2) → `$value` (5) → `$status` (6) → `$precision` (9).
43+
13. No O(N²) or worse complexity exists.
44+
14. No logic is duplicated across two or more places (DRY).
45+
15. No abstraction exists without real duplication or isolation need (KISS).
46+
16. All identifiers, comments, and documentation are written in American English.
47+
17. No justification comments exist (`// NOTE:`, `// REASON:`, etc.). Code speaks for itself.
48+
18. `// TODO: <reason>` is used when implementation is unknown, uncertain, or intentionally deferred.
49+
Never leave silent gaps.
50+
19. All class references use `use` imports at the top of the file. Fully qualified names inline are prohibited.
51+
20. No dead or unused code exists. Remove unreferenced classes, methods, constants, and imports.
52+
21. Never create public methods, constants, or classes in `src/` solely to serve tests. If production code does not
53+
need it, it does not exist.
54+
55+
## Naming
56+
57+
- Names describe **what** in domain terms, not **how** technically: `$monthlyRevenue` instead of `$calculatedValue`.
58+
- Generic technical verbs (`process`, `handle`, `execute`, `mark`, `enforce`, `manage`, `ensure`, `validate`,
59+
`check`, `verify`, `assert`, `transform`, `parse`, `compute`, `sanitize`, `normalize`) **should be avoided**.
60+
Prefer names that describe the domain operation.
61+
- Booleans use predicate form: `isActive`, `hasPermission`, `wasProcessed`.
62+
- Collections are always plural: `$orders`, `$lines`.
63+
- Methods returning bool use prefixes: `is`, `has`, `can`, `was`, `should`.
64+
65+
## Comparisons
66+
67+
1. Null checks: use `is_null($variable)`, never `$variable === null`.
68+
2. Empty string checks on typed `string` parameters: use `$variable === ''`. Avoid `empty()` on typed strings because
69+
`empty('0')` returns `true`.
70+
3. Mixed or untyped checks (value may be `null`, empty string, `0`, or `false`): use `empty($variable)`.
71+
72+
## American English
73+
74+
All identifiers, enum values, comments, and error codes use American English spelling:
75+
`canceled` (not `cancelled`), `organization` (not `organisation`), `initialize` (not `initialise`),
76+
`behavior` (not `behaviour`), `modeling` (not `modelling`), `labeled` (not `labelled`),
77+
`fulfill` (not `fulfil`), `color` (not `colour`).
78+
79+
## PHPDoc
80+
81+
- PHPDoc is restricted to interfaces only, documenting obligations and `@throws`.
82+
- Never add PHPDoc to concrete classes.

.claude/rules/documentation.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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. **License** and **Contributing** sections at the end.
21+
9. Write strictly in American English. See `rules/code-style.md` American English section for spelling conventions.
22+
23+
## Structured data
24+
25+
1. When documenting constructors, factory methods, or configuration options with more than 3 parameters,
26+
use tables with columns: Parameter, Type, Required, Description.
27+
2. Prefer tables to prose for any structured information.
28+
29+
## Style
30+
31+
1. Keep language concise and scannable.
32+
2. Never include placeholder content (`TODO`, `TBD`).
33+
3. Code examples must be syntactically correct and self-contained.
34+
4. Do not document `Internal/` classes or private API. Only document what consumers interact with.

.claude/rules/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()` |

.claude/rules/testing.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
---
2+
description: BDD Given/When/Then structure, PHPUnit conventions, test organization, and fixture rules for PHP libraries.
3+
paths:
4+
- "tests/**/*.php"
5+
---
6+
7+
# Testing conventions
8+
9+
Framework: **PHPUnit**. Refer to `rules/code-style.md` for the code style checklist, which also applies to test files.
10+
11+
## Structure: Given/When/Then (BDD)
12+
13+
Every test uses `/** @Given */`, `/** @And */`, `/** @When */`, `/** @Then */` doc comments without exception.
14+
15+
### Happy path example
16+
17+
```php
18+
public function testAddMoneyWhenSameCurrencyThenAmountsAreSummed(): void
19+
{
20+
/** @Given two money instances in the same currency */
21+
$ten = Money::of(amount: 1000, currency: Currency::BRL);
22+
$five = Money::of(amount: 500, currency: Currency::BRL);
23+
24+
/** @When adding them together */
25+
$total = $ten->add(other: $five);
26+
27+
/** @Then the result contains the sum of both amounts */
28+
self::assertEquals(expected: 1500, actual: $total->amount());
29+
}
30+
```
31+
32+
### Exception example
33+
34+
When testing that an exception is thrown, place `@Then` (expectException) **before** `@When`. PHPUnit requires this
35+
ordering.
36+
37+
```php
38+
public function testAddMoneyWhenDifferentCurrenciesThenCurrencyMismatch(): void
39+
{
40+
/** @Given two money instances in different currencies */
41+
$brl = Money::of(amount: 1000, currency: Currency::BRL);
42+
$usd = Money::of(amount: 500, currency: Currency::USD);
43+
44+
/** @Then an exception indicating currency mismatch should be thrown */
45+
$this->expectException(CurrencyMismatch::class);
46+
47+
/** @When trying to add money with different currencies */
48+
$brl->add(other: $usd);
49+
}
50+
```
51+
52+
Use `@And` for complementary preconditions or actions within the same scenario, avoiding consecutive `@Given` or
53+
`@When` tags.
54+
55+
## Rules
56+
57+
1. Include exactly one `@When` per test. Two actions require two tests.
58+
2. Test only the public API. Never assert on private state or `Internal/` classes directly.
59+
3. Never mock internal collaborators. Use real objects. Use test doubles only at system boundaries (filesystem,
60+
clock, network) when the library interacts with external resources.
61+
4. Name tests to describe behavior, not method names.
62+
5. Never include conditional logic inside tests.
63+
6. Include one logical concept per `@Then` block.
64+
7. Maintain strict independence between tests. No inherited state.
65+
8. For exception tests, place `@Then` (expectException) before `@When`.
66+
9. Use domain-specific model classes in `tests/Models/` for test fixtures that represent domain concepts
67+
(e.g., `Amount`, `Invoice`, `Order`).
68+
10. Use mock classes in `tests/Mocks/` (or `tests/Unit/Mocks/`) for test doubles of system boundaries
69+
(e.g., `ClientMock`, `ExecutionCompletedMock`).
70+
11. Exercise invariants and edge cases through the library's public entry point. Create a dedicated test class
71+
for an internal model only when the condition cannot be reached through the public API.
72+
73+
## Test organization
74+
75+
```
76+
tests/
77+
├── Models/ # Domain-specific fixtures reused across tests
78+
├── Mocks/ # Test doubles for system boundaries
79+
├── Unit/ # Unit tests for public API
80+
│ └── Mocks/ # Alternative location for test doubles
81+
├── Integration/ # Tests requiring real external resources (Docker, filesystem)
82+
└── bootstrap.php # Test bootstrap when needed
83+
```
84+
85+
- `tests/` or `tests/Unit/`: pure unit tests exercising the library's public API.
86+
- `tests/Integration/`: tests requiring real external resources (e.g., Docker containers, databases).
87+
Only present when the library interacts with infrastructure.
88+
- `tests/Models/`: domain-specific fixture classes reused across test files.
89+
- `tests/Mocks/` or `tests/Unit/Mocks/`: test doubles for system boundaries.
90+
91+
## Coverage and mutation testing
92+
93+
1. Line and branch coverage must be **100%**. No annotations (`@codeCoverageIgnore`), attributes, or configuration
94+
that exclude code from coverage are allowed.
95+
2. All mutations reported by Infection must be **killed**. Never ignore or suppress mutants via `infection.json.dist`
96+
or any other mechanism.
97+
3. If a line or mutation cannot be covered or killed, it signals a design problem in the production code. Refactor
98+
the code to make it testable, do not work around the tool.

.github/copilot-instructions.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Copilot instructions
2+
3+
## Context
4+
5+
PHP library (tiny-blocks). Immutable domain models, zero infrastructure dependencies in core.
6+
7+
## Mandatory pre-task step
8+
9+
Before starting any task, read and strictly follow all instruction files located in `.claude/CLAUDE.md` and
10+
`.claude/rules/`. These files are the absolute source of truth for code generation. Apply every rule strictly. Do not
11+
deviate from the patterns, folder structure, or naming conventions defined in them.

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ jobs:
6565
tests:
6666
name: Tests
6767
runs-on: ubuntu-latest
68-
needs: auto-review
68+
needs: build
6969

7070
steps:
7171
- name: Checkout

0 commit comments

Comments
 (0)