|
| 1 | +--- |
| 2 | +description: Folder structure, public API boundary, and Internal/ semantics for PHP libraries. |
| 3 | +paths: |
| 4 | + - "src/**/*.php" |
| 5 | +--- |
| 6 | + |
| 7 | +# Architecture |
| 8 | + |
| 9 | +Covers the physical layout of the library. Folder structure, the boundary between public API and |
| 10 | +implementation detail, and where each type of class lives. Semantic rules (value objects, |
| 11 | +exceptions, enums, complexity, nomenclature) live in `php-library-modeling.md`. Code style lives |
| 12 | +in `php-library-code-style.md`. |
| 13 | + |
| 14 | +## Pre-output checklist |
| 15 | + |
| 16 | +Verify every item before producing or relocating any file. If any item fails, revise before |
| 17 | +outputting. |
| 18 | + |
| 19 | +1. None of the following folder names exist in `src/`: `Models/`, `Entities/`, `ValueObjects/`, |
| 20 | + `Enums/`, `Domain/`. They carry no semantic content and conflate technical role with domain |
| 21 | + meaning. |
| 22 | +2. The `src/` root contains only interfaces, extension points, public enums, thin orchestration |
| 23 | + classes, and primary implementations or façades. Substantial logic (algorithms, state machines, |
| 24 | + I/O) lives in `src/Internal/`, never at the root. |
| 25 | +3. `src/Internal/` is implementation detail and not part of the public API. Breaking changes |
| 26 | + inside `src/Internal/` are not semver-breaking. |
| 27 | +4. Consumers must not reference, extend, or depend on any type inside `src/Internal/`. The |
| 28 | + namespace itself is the boundary. |
| 29 | +5. Public exception classes live in `src/Exceptions/`. |
| 30 | +6. Internal exception classes live in `src/Internal/Exceptions/`. |
| 31 | +7. Public enums live at the `src/` root or inside a public `<ConceptGroup>/` folder. Enums used |
| 32 | + only by internals live in `src/Internal/`. |
| 33 | +8. Public interfaces live at the `src/` root or inside a public `<ConceptGroup>/` folder. |
| 34 | +9. A `<ConceptGroup>/` folder at the `src/` root groups related public types under a shared |
| 35 | + concept. Each group has its own namespace and is part of the public API. |
| 36 | +10. `<ConceptGroup>/` is optional. Use it only when the library exposes several coherent groups of |
| 37 | + types (for example, aggregates and events) rather than a flat set of types around a single |
| 38 | + concept. |
| 39 | +11. Test fixtures representing domain concepts live in `tests/Models/`. Test doubles for system |
| 40 | + boundaries live at the root of `tests/Unit/` or `tests/Integration/`. No dedicated `Mocks/` |
| 41 | + or `Doubles/` subdirectory exists. `tests/Drivers/<Vendor>/` is permitted when the library |
| 42 | + exposes a port exercised against multiple third-party implementations (PSR adapters, |
| 43 | + framework integrations). Each `<Vendor>/` subdir holds tests against one specific |
| 44 | + implementation. |
| 45 | +12. The `tests/Integration/` folder exists only when the library interacts with external |
| 46 | + infrastructure (filesystem, database, network). Otherwise, the folder is absent. |
| 47 | + |
| 48 | +## Folder structure |
| 49 | + |
| 50 | +Canonical layout for a PHP library in the tiny-blocks ecosystem. |
| 51 | + |
| 52 | +``` |
| 53 | +src/ |
| 54 | +├── <PublicInterface>.php # public contract at root |
| 55 | +├── <Implementation>.php # main implementation or extension point at root |
| 56 | +├── <PublicEnum>.php # public enum at root |
| 57 | +├── <ConceptGroup>/ # public folder grouping related public types under a shared concept |
| 58 | +│ ├── <PublicType>.php |
| 59 | +│ └── ... |
| 60 | +├── Internal/ # implementation details, not part of the public API |
| 61 | +│ ├── <Collaborator>.php |
| 62 | +│ └── Exceptions/ # internal exception classes |
| 63 | +└── Exceptions/ # public exception classes |
| 64 | +
|
| 65 | +tests/ |
| 66 | +├── Models/ # domain fixtures reused across tests |
| 67 | +├── Unit/ # unit tests targeting the public API |
| 68 | +│ ├── <SomeMock>.php # test doubles at root of Unit/ |
| 69 | +│ └── <SomeSpy>.php |
| 70 | +└── Integration/ # only present when the library interacts with infrastructure |
| 71 | + └── <SomeMock>.php # test doubles at root of Integration/ when needed |
| 72 | +``` |
| 73 | + |
| 74 | +Never use `Models/`, `Entities/`, `ValueObjects/`, `Enums/`, or `Domain/` as folder names. They |
| 75 | +carry no semantic content and describe technical role instead of domain meaning. |
| 76 | + |
| 77 | +## Public API boundary |
| 78 | + |
| 79 | +The `src/` root is the contract. Everything at the root, plus everything inside public |
| 80 | +`<ConceptGroup>/` folders and the public `Exceptions/` folder, is what consumers depend on. Changes |
| 81 | +to these types follow semver rules. |
| 82 | + |
| 83 | +`src/Internal/` is implementation detail. The namespace itself signals the boundary. Consumers |
| 84 | +must not depend on any type inside `src/Internal/`. Breaking changes inside `src/Internal/` are |
| 85 | +not semver-breaking for the library. |
| 86 | + |
| 87 | +### What lives at the public boundary |
| 88 | + |
| 89 | +- Interfaces that define contracts for consumers. |
| 90 | +- Extension points designed to be subclassed or composed by consumers. |
| 91 | +- Public enums and value objects consumers manipulate directly. |
| 92 | +- Thin orchestration classes that wire collaborators together without containing substantial logic. |
| 93 | +- Public exception classes consumers may catch. |
| 94 | + |
| 95 | +### What lives in `src/Internal/` |
| 96 | + |
| 97 | +- Algorithms, state machines, and complex transformations. |
| 98 | +- Adapters for I/O (filesystem, network, database). |
| 99 | +- Collaborators that exist purely to break a public class into testable units. |
| 100 | +- Implementation details that may change between minor or patch releases. |
| 101 | +- Internal exception classes raised by collaborators. |
| 102 | + |
| 103 | +## Reference examples |
| 104 | + |
| 105 | +### Small library with flat root |
| 106 | + |
| 107 | +``` |
| 108 | +src/ |
| 109 | +├── Timezone.php # public value object |
| 110 | +├── Timezones.php # public collection |
| 111 | +├── Clock.php # public interface |
| 112 | +└── Internal/ |
| 113 | + ├── SystemClock.php # default Clock implementation |
| 114 | + └── Exceptions/ |
| 115 | + └── InvalidTimezone.php |
| 116 | +``` |
| 117 | + |
| 118 | +Everything lives at the root or inside `Internal/`. No `<ConceptGroup>/` folders. Suitable when |
| 119 | +the library exposes a small, cohesive set of types around a single concept. |
| 120 | + |
| 121 | +### Library with public concept groups |
| 122 | + |
| 123 | +``` |
| 124 | +src/ |
| 125 | +├── ValueObject.php # public extension point at root |
| 126 | +├── Aggregate/ # public namespace grouping aggregate types |
| 127 | +│ ├── AggregateRoot.php |
| 128 | +│ ├── EventualAggregateRoot.php |
| 129 | +│ └── ModelVersion.php |
| 130 | +├── Event/ # public namespace grouping event types |
| 131 | +│ ├── EventRecord.php |
| 132 | +│ ├── EventRecords.php |
| 133 | +│ └── SequenceNumber.php |
| 134 | +├── Internal/ |
| 135 | +│ ├── DefaultModelVersionResolver.php |
| 136 | +│ └── Exceptions/ |
| 137 | +│ └── InvalidSequenceNumber.php |
| 138 | +└── Exceptions/ |
| 139 | + └── EventRecordingFailure.php |
| 140 | +``` |
| 141 | + |
| 142 | +`Aggregate/` and `Event/` are public folders at the root, each grouping a coherent set of public |
| 143 | +types under one shared concept. Consumers import directly, for example |
| 144 | +`TinyBlocks\<LibName>\Aggregate\AggregateRoot`. Suitable when the library exposes several distinct |
| 145 | +concept areas, each with its own set of related types. |
0 commit comments