strategy-readonly-immutability
Skill4 starsChanged 4 months agoMITView on GitHub
Prefer immutable value objects and readonly state over mutable shared state; construct fully-formed objects, return new copies instead of mutating, and reserve mutability for the few places that genuinely need it. Use when the task models a value or domain concept — money, a date range, coordinates — as an object whose identity is defined by its contents; the task is to fix or refactor a mutable object whose in-place changes are causing hard-to-trace bugs across callers; the state will be passed around and shared among multiple callers or components that should not surprise one another with mutations. Keywords: strategy, readonly, immutability, immutable, value-object, mutable, shared-state, copy-on-write, const, final.
In this repository119 other files it gives its agents.
- strategy-bitrix-d7-incrementalSkill
- strategy-explicit-over-magicSkill
- strategy-framework-conventionsSkill
- strategy-legacy-safe-editsSkill
- strategy-minimal-diffSkill
- strategy-no-untested-refactorSkill
- strategy-preserve-public-apiSkill
- strategy-security-firstSkill
- strategy-test-drivenSkill
- openapiSkill
- paginationSkill
- rest-designSkill
- versioningSkill
- cqrsSkill
- dependency-inversionSkill
- hexagonalSkill
- repository-patternSkill
- service-layerSkill
- bitrixSkill
- findings-reportSkill
- laravelSkill
- performanceSkill
- phpSkill
- project-analysisSkill
- refactoringSkill
- roi-prioritizationSkill
- securitySkill
- stack-detectionSkill
- cache-layersSkill
- component-engineSkill
- d7-eventsSkill
- d7-loaderSkill
- d7-orm-queriesSkill
- iblock-elementsSkill
- kernel-main-moduleSkill
- localizationSkill
- controllersSkill
- ormSkill
- validationSkill
- controllersSkill
- query-builderSkill
- routingSkill
- autoload-psr4Skill
- dependency-managementSkill
- scriptsSkill
- connection-poolingSkill
- mysql-indexingSkill
- redisSkill
- transactionsSkill
- ci-github-actionsSkill
- deployment-strategiesSkill
- docker-phpSkill
- env-configSkill
- associationsSkill
- entities-mappingSkill
- orm-basicsSkill
- entity-apiSkill
- module-developmentSkill
- routing-controllersSkill
- formsSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
6 rules. About 0 are common, 6 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 68
Do not force immutability on a measured hot path where copying a large structure on each change is a real bottleneck and in-place mutation …
- Distinctive: line 70
Do not insist on immutability for a short-lived local variable used purely to accumulate a result inside one function before returning.
- Distinctive: line 88
Never make a value object's state publicly mutable or share a mutable object across callers when an immutable copy would serve the same …
- Distinctive: line 93
Prefer building fully-formed immutable value objects with readonly fields, and return a modified copy rather than mutating an existing …
- Distinctive: line 98
Avoid adding setters or in-place mutators to value objects.
- Distinctive: line 147
Not applicable — no I/O, auth, or untrusted input.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
<!-- generated: do not edit — source: knowledge/atoms/ai-rules/strategy-readonly-immutability/ --><!-- schema: atomVersion=3 skillVersion=2 graphVersion=1 --><!-- compile: @php-skills/compiler v1.0.0 (deterministic build) -->Strategy — Readonly and Immutability
When to use
- The task models a value or domain concept — money, a date range, coordinates — as an object whose identity is defined by its contents.
- The task is to fix or refactor a mutable object whose in-place changes are causing hard-to-trace bugs across callers.
- The state will be passed around and shared among multiple callers or components that should not surprise one another with mutations.
When NOT to use
- Do not force immutability on a measured hot path where copying a large structure on each change is a real bottleneck and in-place mutation is justified.
- Do not insist on immutability for a short-lived local variable used purely to accumulate a result inside one function before returning.
Core guidance
- Construct fully-formed, valid objects — Require all invariants to be satisfied in the constructor so the object is valid from birth and never passes through a partially-built, invalid state.
- Defensively copy mutable inputs and outputs — When a value object holds a mutable collection or object, copy it on the way in and on the way out so external references cannot mutate the internal state.
- Make value-object fields readonly — Declare a value object's fields as readonly and set them once in the constructor, so no code can change them after construction.
- Confine any needed mutability to a small scope — When mutation is genuinely needed, confine it to a small local scope and produce an immutable result before the value escapes that scope.
- Return a new copy from with-style methods — Model a change as a method that returns a new instance with the field updated, leaving the original untouched, rather than a setter that mutates in place.
Never
- Never make a value object's state publicly mutable or share a mutable object across callers when an immutable copy would serve the same purpose.
Prefer
- Prefer building fully-formed immutable value objects with readonly fields, and return a modified copy rather than mutating an existing instance in place.
Avoid
- Avoid adding setters or in-place mutators to value objects; expose with-style methods that return a new instance with the change applied instead.
Escalate to user when
- Escalate to the user when a design seems to genuinely need shared mutable state — such as a large in-place buffer for performance — and confirm the trade-off before giving up immutability.
Quick recipe
- Declare the fields readonly, set them all in a constructor that enforces the invariants, and copy any mutable inputs defensively. (verify: Every field is readonly, the constructor rejects invalid input, and no mutable input is stored by reference.)
- Replace any setter with a with-style method that returns a new instance carrying the change, and return copies or immutable views of internal collections. (verify: No setter mutates the object in place, each change returns a new instance, and no method leaks a mutable reference to internal state.)
Anti-patterns
| Anti-pattern | Why it fails | Do instead |
| Storing shared mutable state in a global | Global mutable state couples unrelated code through hidden writes and makes behavior<br>depend on execution order, which is fragile and untestable. | Pass immutable values explicitly, and where shared state is unavoidable, wrap it in<br>a small owner with a controlled, minimal mutation surface. |
| Constructing empty then filling with setters | The object exists in an invalid, half-built state where any code that touches it<br>early sees inconsistent data, and it forces mutability that need not exist. | Pass all required values into the constructor so the object is complete and valid<br>the moment it exists. |
| Adding setters to a value object | Setters turn a value into a mutable, aliasable object, reintroducing exactly the<br>shared-mutation bugs immutability was meant to prevent. | Set all fields in the constructor as readonly and expose with-style methods that<br>return new instances for any change. |
See references/anti-patterns.md.
Common mistakes
- Ignoring the return of a with-style method — Calling a with-style method and discarding its return value leaves the original unchanged, so the intended update is silently lost. Fix: Assign the result of every with-style call back to a variable and use that new instance from then on.
- Returning the internal collection breaks immutability — Returning the object's internal mutable array or collection lets the caller change the object's state from the outside. Fix: Return a copy or an immutable view of the collection so external code cannot mutate the internal state.
- A shared mutable object mutated through an alias — Passing a mutable object to several holders lets one of them mutate it and change what all the others see. Fix: Make the object immutable, or hand each holder a defensive copy, so no holder can affect another.
Version notes
- Immutability discipline is version-agnostic (PHP >=7.4) — Preferring immutable value objects over mutable shared state is a strategy that applies to any PHP version; it constrains how the agent shapes state, not which language features it uses, so it holds from PHP 7.4 upward even where the readonly keyword is emulated by convention.
Security
- Not applicable — no I/O, auth, or untrusted input.
Testing
- Activation and decision behavior are verified in
eval/scenarios/ai-rules/strategy-readonly-immutability.yaml.
Additional resources
- references/anti-patterns.md
- references/examples.md
- references/pitfalls.md
- references/recipes.md
- references/version-differences.md
Knowledge graph
Related: ai-rules/strategy-minimal-diff, php/readonly-properties, php/value-objects
<!-- graph-fragment: graph/fragments/ai-rules/strategy-readonly-immutability.yaml -->Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

