|
| 1 | +--- |
| 2 | +description: Pre-output checklist, naming, typing, complexity, 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 `php-library-modeling.md` for library 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 the `tiny-blocks/collection` |
| 33 | + fluent API (`Collection`, `Collectible`) when data is `Collectible`. Use `createLazyFrom` when elements are |
| 34 | + consumed once. Raw arrays are acceptable only for primitive configuration data, variadic pass-through, and |
| 35 | + interop at system boundaries. See "Collection usage" below for the full rule and example. |
| 36 | +10. No private methods exist except private constructors for factory patterns. Inline trivial logic at the call site |
| 37 | + or extract it to a collaborator or value object. |
| 38 | +11. Members are ordered: constants first, then constructor, then static methods, then instance methods. Within each |
| 39 | + group, order by body size ascending (number of lines between `{` and `}`). Constants and enum cases, which have |
| 40 | + no body, are ordered by name length ascending. |
| 41 | +12. Constructor parameters are ordered by parameter name length ascending (count the name only, without `$` or type), |
| 42 | + except when parameters have an implicit semantic order (e.g., `$start/$end`, `$from/$to`, `$startAt/$endAt`), |
| 43 | + which takes precedence. Parameters with default values go last, regardless of name length. The same rule |
| 44 | + applies to named arguments at call sites. |
| 45 | + Example: `$id` (2) → `$value` (5) → `$status` (6) → `$precision` (9). |
| 46 | +13. Time and space complexity are first-class design concerns. |
| 47 | + - No `O(N²)` or worse time complexity exists unless the problem inherently requires it and the cost is |
| 48 | + documented in PHPDoc on the interface method. |
| 49 | + - Space complexity is kept minimal: prefer lazy/streaming pipelines (`createLazyFrom`) over materializing |
| 50 | + intermediate collections. |
| 51 | + - Never re-iterate the same source; fuse stages when possible. |
| 52 | + - Public interface methods document time and space complexity in Big O form (see "PHPDoc" section). |
| 53 | +14. No logic is duplicated across two or more places (DRY). |
| 54 | +15. No abstraction exists without real duplication or isolation need (KISS). |
| 55 | +16. All identifiers, comments, and documentation are written in American English. |
| 56 | +17. No justification comments exist (`// NOTE:`, `// REASON:`, etc.). Code speaks for itself. |
| 57 | +18. `// TODO: <reason>` is used when implementation is unknown, uncertain, or intentionally deferred. |
| 58 | + Never leave silent gaps. |
| 59 | +19. All class references use `use` imports at the top of the file. Fully qualified names inline are prohibited. |
| 60 | +20. No dead or unused code exists. Remove unreferenced classes, methods, constants, and imports. |
| 61 | +21. Never create public methods, constants, or classes in `src/` solely to serve tests. If production code does not |
| 62 | + need it, it does not exist. |
| 63 | +22. Always use the most current and clean syntax available in the target PHP version. Prefer match to switch, |
| 64 | + first-class callables over `Closure::fromCallable()`, readonly promotion over manual assignment, enum methods |
| 65 | + over external switch/if chains, named arguments over positional ambiguity (except where excluded by rule 5), |
| 66 | + and `Collection::map` over foreach accumulation. |
| 67 | +23. No vertical alignment of types in parameter lists or property declarations. Use a single space between |
| 68 | + type and variable name. Never pad with extra spaces to align columns: |
| 69 | + `public OrderId $id` — not `public OrderId $id`. |
| 70 | +24. Opening brace `{` follows PSR-12: on a **new line** for classes, interfaces, traits, enums, and methods |
| 71 | + (including constructors); on the **same line** for closures and control structures (`if`, `for`, `foreach`, |
| 72 | + `while`, `switch`, `match`, `try`). |
| 73 | +25. Never pass an argument whose value equals the parameter's default. Omit the argument entirely. |
| 74 | + Example — `toArray(KeyPreservation $keyPreservation = KeyPreservation::PRESERVE)`: |
| 75 | + `$collection->toArray(keyPreservation: KeyPreservation::PRESERVE)` → `$collection->toArray()`. |
| 76 | + Only pass the argument when the value differs from the default. |
| 77 | +26. No trailing comma in any multi-line list. This applies to parameter lists (constructors, methods, |
| 78 | + closures), argument lists at call sites, array literals, match arms, and any other comma-separated |
| 79 | + multi-line structure. The last element never has a comma after it. PHP accepts trailing commas in |
| 80 | + parameter lists, but this project prohibits them for visual consistency. |
| 81 | + Example — correct: |
| 82 | + ``` |
| 83 | + new Precision( |
| 84 | + value: 2, |
| 85 | + rounding: RoundingMode::HALF_UP |
| 86 | + ); |
| 87 | + ``` |
| 88 | + Example — prohibited: |
| 89 | + ``` |
| 90 | + new Precision( |
| 91 | + value: 2, |
| 92 | + rounding: RoundingMode::HALF_UP, |
| 93 | + ); |
| 94 | + ``` |
| 95 | +
|
| 96 | +## Casing conventions |
| 97 | +
|
| 98 | +- Internal code (variables, methods, classes): **`camelCase`**. |
| 99 | +- Constants and enum-backed values when representing codes: **`SCREAMING_SNAKE_CASE`**. |
| 100 | +
|
| 101 | +## Naming |
| 102 | +
|
| 103 | +- Names describe **what** in domain terms, not **how** technically: `$monthlyRevenue` instead of `$calculatedValue`. |
| 104 | +- Generic technical verbs are avoided. See `php-library-modeling.md` — Nomenclature. |
| 105 | +- Booleans use predicate form: `isActive`, `hasPermission`, `wasProcessed`. |
| 106 | +- Collections are always plural: `$orders`, `$lines`. |
| 107 | +- Methods returning bool use prefixes: `is`, `has`, `can`, `was`, `should`. |
| 108 | +
|
| 109 | +## Comparisons |
| 110 | +
|
| 111 | +1. Null checks: use `is_null($variable)`, never `$variable === null`. |
| 112 | +2. Empty string checks on typed `string` parameters: use `$variable === ''`. Avoid `empty()` on typed strings |
| 113 | + because `empty('0')` returns `true`. |
| 114 | +3. Mixed or untyped checks (value may be `null`, empty string, `0`, or `false`): use `empty($variable)`. |
| 115 | +
|
| 116 | +## American English |
| 117 | +
|
| 118 | +All identifiers, enum values, comments, and error codes use American English spelling: |
| 119 | +`canceled` (not `cancelled`), `organization` (not `organisation`), `initialize` (not `initialise`), |
| 120 | +`behavior` (not `behaviour`), `modeling` (not `modelling`), `labeled` (not `labelled`), |
| 121 | +`fulfill` (not `fulfil`), `color` (not `colour`). |
| 122 | +
|
| 123 | +## PHPDoc |
| 124 | +
|
| 125 | +- PHPDoc is restricted to interfaces only, documenting obligations, `@throws`, and complexity. |
| 126 | +- Never add PHPDoc to concrete classes. |
| 127 | +- Document `@throws` for every exception the method may raise. |
| 128 | +- Document time and space complexity in Big O form. When a method participates in a fused pipeline (e.g., collection |
| 129 | + pipelines), express cost as a two-part form: call-site cost + fused-pass contribution. Include a legend defining |
| 130 | + variables (e.g., `N` for input size, `K` for number of stages). |
| 131 | +
|
| 132 | +## Collection usage |
| 133 | +
|
| 134 | +When a property or parameter is `Collectible`, use its fluent API. Never break out to raw array functions such as |
| 135 | +`array_map`, `array_filter`, `iterator_to_array`, or `foreach` + accumulation. The same applies to `filter()`, |
| 136 | +`reduce()`, `each()`, and all other `Collectible` operations. Chain them fluently. Never materialize with |
| 137 | +`iterator_to_array` to then pass into a raw `array_*` function. |
| 138 | +
|
| 139 | +**Prohibited — `array_map` + `iterator_to_array` on a Collectible:** |
| 140 | +
|
| 141 | +```php |
| 142 | +$names = array_map( |
| 143 | + static fn(Element $element): string => $element->name(), |
| 144 | + iterator_to_array($collection) |
| 145 | +); |
| 146 | +``` |
| 147 | + |
| 148 | +**Correct — fluent chain with `map()` + `toArray()`:** |
| 149 | + |
| 150 | +```php |
| 151 | +$names = $collection |
| 152 | + ->map(transformations: static fn(Element $element): string => $element->name()) |
| 153 | + ->toArray(keyPreservation: KeyPreservation::DISCARD); |
| 154 | +``` |
0 commit comments