Skip to content

Commit 9901287

Browse files
authored
Release/4.9.2 (#55)
1 parent 6c4b035 commit 9901287

4 files changed

Lines changed: 90 additions & 47 deletions

File tree

.claude/rules/php-library-modeling.md

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,39 @@ Breaking changes inside `Internal/` are not semver-breaking for the library.
4040
1. Every class, property, method, and exception name reflects the **concept** the library represents. A math library
4141
uses `Precision`, `RoundingMode`; a money library uses `Currency`, `Amount`; a collection library uses
4242
`Collectible`, `Order`.
43-
2. Never use generic technical names as class suffixes, prefixes, or method names: `Manager`, `Helper`, `Processor`,
44-
`Handler`, `Service`, `Data`, `Info`, `Utils`, `Item`, `Record`, `Entity`, `Exception`, `process`, `handle`,
45-
`execute`, `mark`, `enforce`, `manage`, `ensure`, `validate`, `check`, `verify`, `assert`, `transform`, `parse`,
46-
`compute`, `sanitize`, or `normalize`.
47-
3. Name classes after what they represent: `Money`, `Color`, `Pipeline` — not after what they do technically.
48-
4. Name methods after the operation in its vocabulary: `add()`, `convertTo()`, `splitAt()`.
43+
2. Name classes after what they represent: `Money`, `Color`, `Pipeline` — not after what they do technically.
44+
3. Name methods after the operation in the library's vocabulary: `add()`, `convertTo()`, `splitAt()`.
45+
46+
### Always banned
47+
48+
These names carry zero semantic content. Never use them anywhere, as class suffixes, prefixes, or method names:
49+
50+
- `Data`, `Info`, `Utils`, `Item`, `Record`, `Entity`.
51+
- `Exception` as a class suffix (e.g., `FooException` — use `Foo` when it already extends a native exception).
52+
53+
### Anemic verbs (banned by default)
54+
55+
These verbs hide what is actually happening behind a generic action. Banned unless the verb **is** the operation
56+
that constitutes the library's reason to exist (e.g., a JSON parser may have `parse()`; a hashing library may
57+
have `compute()`):
58+
59+
- `ensure`, `validate`, `check`, `verify`, `assert`, `mark`, `enforce`, `sanitize`, `normalize`, `compute`,
60+
`transform`, `parse`.
61+
62+
When in doubt, prefer the domain operation name. `Password::hash()` beats `Password::compute()`; `Email::parse()`
63+
is fine in a parser library but suspicious elsewhere (use `Email::from()` instead).
64+
65+
### Architectural roles (allowed with justification)
66+
67+
These names describe a role the library offers as a building block. Acceptable when the class **is** that role
68+
(e.g., `EventHandler` in an events library, `CacheManager` in a cache library, `Upcaster` in an event-sourcing
69+
library). Not acceptable on domain objects inside the library (value objects, enums, contract interfaces):
70+
71+
- `Manager`, `Handler`, `Processor`, `Service`, and their verb forms `process`, `handle`, `execute`.
72+
73+
The test: if the consumer instantiates or extends this class to integrate with the library, the role name is
74+
legitimate. If the class models a concept the consumer manipulates (a money amount, a country code, a color),
75+
the role name is wrong.
4976

5077
## Value objects
5178

@@ -60,20 +87,23 @@ Breaking changes inside `Internal/` are not semver-breaking for the library.
6087

6188
1. Every failure throws a **dedicated exception class** named after the invariant it guards — never
6289
`throw new DomainException('...')`, `throw new InvalidArgumentException('...')`,
63-
`throw new RuntimeException('...')`, or any other generic native exception with a string message. If the
64-
invariant is worth throwing for, it is worth a named class.
90+
`throw new RuntimeException('...')`, or any other generic native exception thrown directly. If the invariant
91+
is worth throwing for, it is worth a named class.
6592
2. Dedicated exception classes **extend** the appropriate native PHP exception (`DomainException`,
6693
`InvalidArgumentException`, `OverflowException`, etc.) — the native class is the parent, never the thing that
6794
is thrown. Consumers that catch the broad standard types continue to work; consumers that need precise handling
6895
can catch the specific classes.
69-
3. Exceptions are pure: no transport-specific fields (`code`, formatted `message`). Formatting to any transport
70-
happens at the consumer's boundary, not inside the library.
96+
3. Exceptions are pure: no transport-specific fields (`code` populated with HTTP status, formatted `message` meant
97+
for end-user display). Formatting to any transport happens at the consumer's boundary, not inside the library.
7198
4. Exceptions signal invariant violations only, not control flow.
7299
5. Name the class after the invariant violated, never after the technical type:
73100
- `PrecisionOutOfRange` — not `InvalidPrecisionException`.
74101
- `CurrencyMismatch` — not `BadCurrencyException`.
75102
- `ContainerWaitTimeout` — not `TimeoutException`.
76-
6. No exception-formatting constructor, no custom message argument — the class name is the message.
103+
6. A descriptive `message` argument is allowed and encouraged when it carries **debugging context** — the violating
104+
value, the boundary that was crossed, the state the library was in. The class name identifies the invariant;
105+
the message describes the specific violation for stack traces and test assertions. Do not build messages meant
106+
for end-user display or transport rendering. Keep them short, factual, and in American English.
77107
7. Public exceptions live in `src/Exceptions/`. Internal exceptions live in `src/Internal/Exceptions/`.
78108

79109
**Prohibited** — throwing a native exception directly:
@@ -84,7 +114,7 @@ if ($value < 0) {
84114
}
85115
```
86116

87-
**Correct** — dedicated class extending the native exception:
117+
**Correct** — dedicated class, no message (class name is sufficient):
88118

89119
```php
90120
// src/Exceptions/PrecisionOutOfRange.php
@@ -98,6 +128,14 @@ if ($value < 0) {
98128
}
99129
```
100130

131+
**Correct** — dedicated class with debugging context:
132+
133+
```php
134+
if ($value < 0 || $value > 16) {
135+
throw new PrecisionOutOfRange(sprintf('Precision must be between 0 and 16, got %d.', $value));
136+
}
137+
```
138+
101139
## Enums
102140

103141
1. Are PHP backed enums.

.gitattributes

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,22 @@
1-
# Auto detect text files and perform LF normalization
21
* text=auto eol=lf
32

4-
# ─── Diff drivers ────────────────────────────────────────────
5-
*.php diff=php
6-
*.md diff=markdown
3+
*.php text diff=php
74

8-
# ─── Force LF ────────────────────────────────────────────────
9-
*.sh text eol=lf
10-
Makefile text eol=lf
11-
12-
# ─── Generated (skip diff and GitHub stats) ──────────────────
13-
composer.lock -diff linguist-generated
14-
15-
# ─── Export ignore (excluded from dist archive) ──────────────
16-
/tests export-ignore
17-
/vendor export-ignore
18-
/rules export-ignore
19-
20-
/.github export-ignore
21-
/.gitignore export-ignore
22-
/.gitattributes export-ignore
23-
24-
/CLAUDE.md export-ignore
25-
/LICENSE export-ignore
26-
/Makefile export-ignore
27-
/README.md export-ignore
28-
/phpunit.xml export-ignore
29-
/phpstan.neon.dist export-ignore
30-
/infection.json.dist export-ignore
5+
# Dev-only — excluded from the Packagist tarball
6+
/.github export-ignore
7+
/tests export-ignore
8+
/.claude export-ignore
9+
/.editorconfig export-ignore
10+
/.gitattributes export-ignore
11+
/.gitignore export-ignore
12+
/phpunit.xml export-ignore
13+
/phpunit.xml.dist export-ignore
14+
/phpstan.neon export-ignore
15+
/phpstan.neon.dist export-ignore
16+
/phpcs.xml export-ignore
17+
/phpcs.xml.dist export-ignore
18+
/infection.json export-ignore
19+
/infection.json.dist export-ignore
20+
/Makefile export-ignore
21+
/CONTRIBUTING.md export-ignore
22+
/CHANGES.md export-ignore

.gitignore

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
1-
.idea
1+
# Agent/IDE
2+
.claude/
3+
.idea/
4+
.vscode/
5+
.cursor/
26

3-
vendor
4-
report
7+
# Composer
8+
/vendor/
9+
composer.lock
510

6-
*.lock
7-
.phpunit.*
11+
# PHPUnit / coverage
12+
.phpunit.cache/
13+
.phpunit.result.cache
14+
report/
15+
coverage/
16+
build/
17+
18+
# OS
19+
.DS_Store
20+
Thumbs.db

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@
2222
"require-dev": {
2323
"ergebnis/composer-normalize": "^2.51",
2424
"infection/infection": "^0.32",
25-
"laminas/laminas-httphandlerrunner": "^2.12",
25+
"laminas/laminas-httphandlerrunner": "^2.13",
2626
"phpstan/phpstan": "^2.1",
2727
"phpunit/phpunit": "^13.1",
28-
"slim/psr7": "^1.7",
29-
"slim/slim": "^4.14",
28+
"slim/psr7": "^1.8",
29+
"slim/slim": "^4.15",
3030
"squizlabs/php_codesniffer": "^4.0"
3131
},
3232
"minimum-stability": "stable",

0 commit comments

Comments
 (0)