Skip to content

Commit c24a254

Browse files
committed
chore: simplify documentation
1 parent da5b7f1 commit c24a254

138 files changed

Lines changed: 4826 additions & 4853 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎documentation/adrs.md‎

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,32 @@
44

55
# What are ADRs?
66

7-
Architecture Decision Records (ADRs) are structured documents that capture significant architectural decisions made
8-
during the development of this project.
7+
Architecture Decision Records (ADRs) are structured documents. They record the important architectural decisions of
8+
this project.
99

1010
Each ADR explains:
1111

12-
- **The context**: Why the decision was necessary.
13-
- **The decision**: What was decided and why.
14-
- **The consequences**: The impact of the decision, both positive and negative.
12+
- The context: why the decision was necessary.
13+
- The decision: what was decided and why.
14+
- The consequences: the positive and negative effects of the decision.
1515

16-
ADRs act as a single source of truth for important design and architectural choices, ensuring that everyone involved in
17-
the project has a shared understanding of why things are the way they are.
16+
ADRs are the single source of truth for important design and architectural choices. Everyone in the project can
17+
read in them why things are the way they are.
1818

1919
## How to Use ADRs in This Project
2020

21-
**Follow Existing ADRs**: ADRs are mandatory to follow unless explicitly overridden by a new ADR. This ensures
22-
consistency in decision-making across the project.
21+
Follow existing ADRs: you must follow each ADR. Only a new ADR can explicitly override it. This keeps the
22+
decisions in the project consistent.
2323

24-
> Whenever ADR is merged into the main branch, it is considered accepted and must be followed by all contributors.
24+
> When an ADR is merged into the main branch, it is accepted. All contributors must then follow it.
2525
26-
**Propose New ADRs for Significant Decisions**:
26+
Propose new ADRs for important decisions:
2727

28-
- If you encounter a situation that requires a significant architectural or design change, you must create a new ADR.
29-
- Use the provided [ADR Template](/documentation/adrs/template.md) to create your proposal.
30-
- Submit the ADR via a pull request and engage in a discussion with maintainers and contributors.
31-
- **Document Decisions Transparently**: All significant decisions must be documented. This includes not only what was
32-
decided but also the alternatives that were considered and why they were rejected.
28+
- If a situation needs an important architectural or design change, you must create a new ADR.
29+
- Use the [ADR Template](/documentation/adrs/template.md) for your proposal.
30+
- Send the ADR as a pull request, and discuss it with maintainers and contributors.
31+
- Document decisions openly: all important decisions must be documented. The document says what was decided, and
32+
also which alternatives were considered and why they were rejected.
3333

3434
## Index of ADRs
3535

‎documentation/adrs/extension-points.md‎

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,69 +8,64 @@ Date: 2025-01-09
88
## Context
99
---
1010

11-
Flow is a framework, and as all frameworks, it should provide extension points for developers to hook into the framework and extend its functionality.
11+
Flow is a framework. As all frameworks, it must give developers extension points, where they can connect to the framework and extend it.
1212

1313
How to find an extension point?
1414

15-
Easy, look for interfaces! For example take a look at `\Flow\ETL\Storage\RowsStorage` which is
16-
an interface with few implementations. Thanks to the interface, you can easily
17-
create your own implementation and use it in the framework.
15+
Look for interfaces. An example is `\Flow\ETL\Storage\RowsStorage`, an interface with a few implementations.
16+
Because of the interface, you can create your own implementation and use it in the framework.
1817

19-
Most of the extension points exposed through `\Flow\ETL\Runtime\Config` where you can
20-
replace a default implementations of various interfaces with those that better
21-
fit your needs.
18+
Most of the extension points are in `\Flow\ETL\Runtime\Config`. There, you can replace the default
19+
implementations of different interfaces with implementations that fit your needs better.
2220

23-
Those extension points are there by design and should follow the backward compatibility policy.
21+
Those extension points exist by design, and they must follow the backward compatibility policy.
2422

25-
Are there any other extension points that we should consider?
23+
Are there other extension points to think about?
2624

27-
Yes... Unfortunately, each class that is not marked final and that is being somehow
28-
injected into the DataFrame must be considered as an extension point.
25+
Yes. Each class that is not marked final, and that goes into the DataFrame in some way, is also an extension
26+
point.
2927

30-
Why? Because nothing prevents developers from extending those classes and injecting
31-
their custom version into the DataFrame.
28+
The reason: nothing stops developers from extending those classes and putting their custom version into the
29+
DataFrame.
3230

33-
Those extension points can't be covered by our backward compatibility policy.
31+
Our backward compatibility policy cannot cover those extension points.
3432

3533
## Decision
3634
---
3735

38-
**Closed by default**
36+
Closed by default:
3937

40-
All classes should be marked as `final` and whenever we need to expose an extension point,
41-
we should do it through an interface.
38+
All classes must be marked as `final`. Each extension point that we need must be an interface.
4239

43-
**Always possible to open later**
40+
Always possible to open later:
4441

45-
In a justifiable cases, we can expose extension points through abstract classes or
46-
simply allowing people to extend the class. Those must always be approved by one of
47-
the core contributors.
42+
In justified cases, we can give extension points through abstract classes, or allow people to extend the class. One
43+
of the core contributors must always approve these cases.
4844

49-
**No guarantee and support for workarounds**
45+
No guarantee and no support for workarounds:
5046

51-
Developers can still use reflections or dedicated tools to "unfinalize" Flow classes,
52-
but we do not guarantee backward compatibility for those cases and everyone should
53-
do it at their own risk. As a Flow PHP maintainers, we also reserve the right to
54-
not provide any help or support for those cases.
47+
Developers can still use reflection or special tools to "unfinalize" Flow classes. We do not guarantee backward
48+
compatibility for those cases, and each developer does it at their own risk. As Flow PHP maintainers, we also keep the
49+
right to give no help or support for those cases.
5550

5651
## Pros & Cons
5752
---
5853

59-
- zero risk of breaking accidentally backward compatibility promise
54+
- no risk to break the backward compatibility promise by accident
6055
- more predictable behavior
61-
- reduced cost of maintaining backward compatibility
62-
- easier to find an actual extension points
63-
- impossible to mock classes that aren't marked as `final` in tests (which is a good thing, users shouldn't mock Flow classes in their test suites)
56+
- lower cost to keep backward compatibility
57+
- easier to find the real extension points
58+
- impossible to mock classes that are marked as `final` in tests (which is good, because users must not mock Flow classes in their test suites)
6459

6560
## Alternatives Considered (optional)
6661
---
6762

68-
Alternatively we could take similar approach to Symfony and mark classes as `final`
69-
[when it makes sense](https://github.com/symfony/symfony/issues/15233#issuecomment-1566682054)
70-
but this approach is too easy to get abused.
63+
An alternative is the approach of Symfony: mark classes as `final`
64+
[when it makes sense](https://github.com/symfony/symfony/issues/15233#issuecomment-1566682054).
65+
But this approach is too easy to abuse.
7166

72-
Plus, changing non-final class into a final one is much bigger BC break
73-
then the other way around.
67+
Also, a change from a non-final class to a final class is a much bigger BC break
68+
than the other way around.
7469

7570
## Links and References (optional)
7671
---

‎documentation/adrs/schema-immutability.md‎

Lines changed: 55 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -8,60 +8,63 @@ Date: 2026-07-27
88
## Context
99
---
1010

11-
`Schema` was mutable - `add()`, `remove()`, `rename()`, `merge()` and every other mutator rewrote
11+
`Schema` was mutable. `add()`, `remove()`, `rename()`, `merge()` and all other mutators rewrote
1212
`$this->definitions` and returned `$this`. `Definition::addMetadata()` and `Definition::setMetadata()` did the same
1313
with `$this->metadata`.
1414

15-
File extractors extend the schema they were given so the hydrator can cast auto-added columns
16-
(`_input_file_uri`, partition columns, sheet metadata): `Hydrator::cast()` iterates schema definitions and drops
17-
undeclared row keys, so a column that should materialize in rows must be present in the schema. An extractor
18-
storing the caller's `Schema` therefore wrote every internal extension into the caller's object, producing three
19-
observable defects (#2536 regression):
15+
File extractors extend the schema that they get, so that the hydrator can cast auto-added columns
16+
(`_input_file_uri`, partition columns, sheet metadata). `Hydrator::cast()` iterates the schema definitions and drops
17+
undeclared row keys. A column that must appear in rows must therefore be in the schema. An extractor that stored the
18+
`Schema` of the caller therefore wrote each internal extension into the object of the caller. This caused three
19+
visible defects (#2536 regression):
2020

21-
1. **Caller schema pollution** - a `Schema` the user holds for other purposes gains non-nullable columns it never
21+
1. Caller schema pollution: a `Schema` that the user holds for other purposes gets non-nullable columns that it never
2222
declared.
23-
2. **Extractor-lifetime pollution** - columns added during one `extract()` run persist into subsequent runs.
24-
3. **Cross-stream pollution** - partition columns of one stream leak into the next, and
23+
2. Extractor-lifetime pollution: columns added during one `extract()` run stay in the next runs.
24+
3. Cross-stream pollution: partition columns of one stream go into the next stream, and
2525
`Hydrator::cast(fillMissing: true)` injects `null` into a non-nullable definition.
2626

27-
The first fix cloned: `withSchema()` stored `clone $schema`, and extractors cloned again per run and per stream.
28-
That fix was incomplete. `clone` is shallow and `Schema`'s only state is `array<string, Definition>`, so a cloned
29-
`Schema` **shares its `Definition` instances**. `Schema::addMetadata()` / `setMetadata()` reached into a shared
30-
`Definition` and mutated it in place, so metadata writes aliased through every copy - including the caller's.
27+
The first fix cloned. `withSchema()` stored `clone $schema`, and extractors cloned again for each run and each
28+
stream. That fix was incomplete. `clone` is shallow, and the only state of `Schema` is `array<string, Definition>`. A
29+
cloned `Schema` therefore shares its `Definition` instances. `Schema::addMetadata()` / `setMetadata()` changed a
30+
shared `Definition` in place, so each metadata write went through to all copies, also the copy of the caller.
3131

32-
The same mutable contract left latent aliasing traps elsewhere: `Rows::schema()` seeded its merge loop with row 0's
33-
*memoized* `Schema` and corrupted it, `FloeStreamWriter` retained a caller-owned `Schema` for the lifetime of a
34-
write session, and `merge()`'s fast paths returned `$this` or the argument.
32+
The same mutable contract left hidden aliasing traps in other places:
33+
34+
- `Rows::schema()` started its merge loop with the memoized `Schema` of row 0 and corrupted it.
35+
- `FloeStreamWriter` kept a `Schema` of the caller for the lifetime of a write session.
36+
- The fast paths of `merge()` returned `$this` or the argument.
3537

3638
## Decision
3739
---
3840

39-
**`Schema` and its whole state chain are immutable. Every mutator returns a new instance; nothing is ever written
40-
in place.**
41+
`Schema` and its full state chain are immutable. Each mutator returns a new instance. Nothing is ever written in
42+
place.
4143

42-
- `Schema` - a `final readonly class`. All 19 mutators return `new self(...)`; `setDefinitions()` is the
43-
constructor's validation helper and is called from the constructor only.
44-
- `Definition` (19 implementations) - each a `final readonly class`. `addMetadata()` and `setMetadata()` return a
45-
per-class `new self(...)`, matching the idiom `makeNullable()` and `rename()` already used.
46-
- `Metadata` - already a `final readonly class`.
44+
- `Schema`: a `final readonly class`. All 19 mutators return `new self(...)`. `setDefinitions()` is the validation
45+
helper of the constructor, and only the constructor calls it.
46+
- `Definition` (19 implementations): each one is a `final readonly class`. `addMetadata()` and `setMetadata()` return
47+
a `new self(...)` of their own class. `makeNullable()` and `rename()` already used this idiom.
48+
- `Metadata`: already a `final readonly class`.
4749

48-
Immutability is declared at the class level, not per property: a `readonly class` cannot gain a writable property
49-
later, so the guarantee survives future edits instead of depending on whoever adds property number 20 remembering
50-
the rule. It is compiler-enforced, not convention. Extractors and the DSL hold caller-provided `Schema` instances
51-
directly: there is nothing to clone because there is nothing to mutate. Sharing an instance - `merge()`'s fast
52-
paths, a retained base `Definition` in the hydrator, `Rows`' memoized schema - is safe by construction.
50+
Immutability is declared at the class level, not for each property. A `readonly class` cannot get a writable
51+
property later. The guarantee therefore survives future edits. It does not depend on the person who adds property
52+
number 20 and remembers the rule. The compiler enforces it, so it is not a convention. Extractors and the DSL hold the
53+
`Schema` instances of the caller directly. There is nothing to clone, because there is nothing to mutate. Shared
54+
instances are safe by construction. Examples are the fast paths of `merge()`, a kept base `Definition` in the
55+
hydrator, and the memoized schema of `Rows`.
5356

5457
DSL `from_*()` functions stay pure delegation.
5558

5659
### Out of scope
5760

58-
`UnresolvedReference` remains mutable - `as()`, `asc()` and `desc()` write `$alias` / `$sort` on `$this`. It is shared
59-
with the entire expression DSL, so making it immutable is a separate project and is not attempted here.
61+
`UnresolvedReference` stays mutable. `as()`, `asc()` and `desc()` write `$alias` / `$sort` on `$this`. The full
62+
expression DSL shares it, so its immutability is a separate project. This decision does not try it.
6063

6164
### Breaking change
6265

63-
Calling a mutator and discarding the result is now a **silent no-op**. There is no `#[\NoDiscard]`; the change is
64-
communicated through [upgrading.md](/documentation/upgrading.md).
66+
A call to a mutator that discards the result is now a silent no-op. There is no `#[\NoDiscard]`.
67+
[upgrading.md](/documentation/upgrading.md) tells users about the change.
6568

6669
```php
6770
$schema->add(str_schema('x')); // before: mutates $schema. now: no-op.
@@ -71,46 +74,46 @@ $schema = $schema->add(str_schema('x')); // correct
7174
## Pros & Cons
7275
---
7376

74-
**Advantages:**
77+
Advantages:
7578

76-
- **The bug class is gone**, not patched - aliasing is impossible because there is no writable state to alias.
77-
- **Compiler-enforced**: a `readonly` violation is a fatal error, not a convention a new extractor can forget.
78-
- **Sharing becomes free**: no defensive clones in extractors, `PhpRowHydrator`, or the native hydrator.
79+
- The bug class is gone, not patched. Aliasing is impossible, because there is no writable state to alias.
80+
- Compiler-enforced: a `readonly` violation is a fatal error, not a convention that a new extractor can forget.
81+
- Sharing is free: no defensive clones in extractors, `PhpRowHydrator`, or the native hydrator.
7982
- Fixes the `Rows::schema()` and `FloeStreamWriter` aliasing traps without touching either.
8083

81-
**Disadvantages:**
84+
Disadvantages:
8285

83-
- Breaking change for downstream code that discards a mutator's return value, and it breaks **silently**.
84-
- A long mutator chain allocates one `Schema` per link. Schemas are small and built once per pipeline, not per
85-
row, so this does not show up in profiles.
86+
- Breaking change for downstream code that discards the return value of a mutator, and it breaks silently.
87+
- A long mutator chain allocates one `Schema` for each link. Schemas are small and built once for each pipeline, not
88+
for each row, so this does not show in profiles.
8689

8790
## Alternatives Considered
8891
---
8992

9093
### 1. Clone at reception, clone before extending
9194

92-
Every `withSchema()` stores `clone $schema`; extractors clone again per run and per stream.
95+
Each `withSchema()` stores `clone $schema`. Extractors clone again for each run and each stream.
9396

94-
**Rejected because:** the clone is shallow, so `Definition` instances stay shared and metadata mutations alias
95-
through every copy anyway. It is also convention rather than a compiler-enforced rule - a new extractor can forget
96-
to clone - and it leaves unobservable dead clones in extractors that never extend the schema.
97+
Rejected because: the clone is shallow, so `Definition` instances stay shared, and metadata mutations still go
98+
through to all copies. It is also a convention and not a rule that the compiler enforces. A new extractor can forget
99+
to clone. It also leaves invisible dead clones in extractors that never extend the schema.
97100

98101
### 2. Deep `Schema::__clone()` + `Definition::__clone()`
99102

100-
Give `Schema` and every `Definition` a `__clone()` that copies the definition array and its objects.
103+
Give `Schema` and each `Definition` a `__clone()` that copies the definition array and its objects.
101104

102-
**Rejected because:** it patches the symptom while keeping the mutable contract, so every future aliasing trap
103-
(`Rows::schema()`, `FloeStreamWriter`, `merge()`'s fast paths) still has to be found and cloned around by hand. It
104-
also makes every clone more expensive without removing the need to remember to clone.
105+
Rejected because: it patches the symptom and keeps the mutable contract. Each future aliasing trap
106+
(`Rows::schema()`, `FloeStreamWriter`, the fast paths of `merge()`) must still be found and cloned around by hand. It
107+
also makes each clone more expensive, and developers must still remember to clone.
105108

106109
### 3. Materialize auto-added columns post-hydration via `Row::add()` (Floe style)
107110

108-
**Rejected because:** the extended schema *is* the hydrator's instruction set - `Hydrator::cast()` drops undeclared
109-
row keys, and the `findDefinition()` guard lets a user-declared partition column keep its user-defined type.
110-
Post-hydration adds would bypass both.
111+
Rejected because: the extended schema is the instruction set of the hydrator. `Hydrator::cast()` drops undeclared
112+
row keys. The `findDefinition()` guard lets a partition column that the user declared keep its user-defined type.
113+
Adds after hydration go around both.
111114

112115
## Links and References
113116
---
114117

115-
- [PR #2536](https://github.com/flow-php/flow/pull/2536) - encoder/hydrator row contract that introduced the
118+
- [PR #2536](https://github.com/flow-php/flow/pull/2536): the encoder/hydrator row contract that added the
116119
schema extension in extractors

0 commit comments

Comments
 (0)