Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions guide/en/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# The definitive guide to Yii 3.0

We release this guide under the [Terms of Yii Documentation](https://www.yiiframework.com/license#docs).

Check warning on line 3 in guide/en/README.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/README.md#L3

[Microsoft.We] Try to avoid using first-person plural like 'We'.
Raw output
{"message": "[Microsoft.We] Try to avoid using first-person plural like 'We'.", "location": {"path": "guide/en/README.md", "range": {"start": {"line": 3, "column": 1}}}, "severity": "WARNING"}

Introduction +
------------
Expand Down Expand Up @@ -42,11 +42,12 @@
* [Configuration](concept/configuration.md) +
* [Aliases](concept/aliases.md) +
* [Events](concept/events.md) +
* [Immutability](concept/immutability.md) +

Handling requests +
-----------------

* [Routing and URL generation](runtime/routing.md) +

Check warning on line 50 in guide/en/README.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/README.md#L50

[Microsoft.GeneralURL] For a general audience, use 'address' rather than 'URL'.
Raw output
{"message": "[Microsoft.GeneralURL] For a general audience, use 'address' rather than 'URL'.", "location": {"path": "guide/en/README.md", "range": {"start": {"line": 50, "column": 16}}}, "severity": "WARNING"}
* [Request](runtime/request.md) +
* [Response](runtime/response.md) +
* [Sessions](runtime/sessions.md) +
Expand All @@ -71,7 +72,7 @@

* [Database access objects](db-dao.md): Connecting to a database, basic queries, transactions, and schema manipulation
* [Query builder](db-query-builder.md): Querying the database using a simple abstraction layer
* [Active record](db-active-record.md): The Active Record ORM, retrieving and manipulating records, and defining relations

Check notice on line 75 in guide/en/README.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/README.md#L75

[Microsoft.Acronyms] 'ORM' has no definition.
Raw output
{"message": "[Microsoft.Acronyms] 'ORM' has no definition.", "location": {"path": "guide/en/README.md", "range": {"start": {"line": 75, "column": 59}}}, "severity": "INFO"}
* [Migrations](databases/db-migrations.md): +

Getting data from users -
Expand Down Expand Up @@ -113,7 +114,7 @@
* [HTTP caching](caching/http.md) -


REST APIs -

Check warning on line 117 in guide/en/README.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/README.md#L117

[Microsoft.HeadingAcronyms] Avoid using acronyms in a title or heading.
Raw output
{"message": "[Microsoft.HeadingAcronyms] Avoid using acronyms in a title or heading.", "location": {"path": "guide/en/README.md", "range": {"start": {"line": 117, "column": 1}}}, "severity": "WARNING"}

Check notice on line 117 in guide/en/README.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/README.md#L117

[Microsoft.Headings] 'REST APIs -' should use sentence-style capitalization.
Raw output
{"message": "[Microsoft.Headings] 'REST APIs -' should use sentence-style capitalization.", "location": {"path": "guide/en/README.md", "range": {"start": {"line": 117, "column": 1}}}, "severity": "INFO"}

Check notice on line 117 in guide/en/README.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/README.md#L117

[Microsoft.Acronyms] 'REST' has no definition.
Raw output
{"message": "[Microsoft.Acronyms] 'REST' has no definition.", "location": {"path": "guide/en/README.md", "range": {"start": {"line": 117, "column": 1}}}, "severity": "INFO"}
-----------

* [Quick start](rest/quick-start.md)
Expand Down
151 changes: 151 additions & 0 deletions guide/en/concept/immutability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# Immutability

Immutability means an object's state cannot change after it has been created.

Check failure on line 3 in guide/en/concept/immutability.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/concept/immutability.md#L3

[Microsoft.Contractions] Use 'can't' instead of 'cannot'.
Raw output
{"message": "[Microsoft.Contractions] Use 'can't' instead of 'cannot'.", "location": {"path": "guide/en/concept/immutability.md", "range": {"start": {"line": 3, "column": 38}}}, "severity": "ERROR"}

Check notice on line 3 in guide/en/concept/immutability.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/concept/immutability.md#L3

[Microsoft.Passive] 'been created' looks like passive voice.
Raw output
{"message": "[Microsoft.Passive] 'been created' looks like passive voice.", "location": {"path": "guide/en/concept/immutability.md", "range": {"start": {"line": 3, "column": 65}}}, "severity": "INFO"}
Instead of modifying an instance, you create a new instance with the desired changes.
This approach is common for value objects such as Money, IDs, and DTOs. It helps to avoid accidental side effects:
methods cannot silently change shared state, which makes code easier to reason about.

Check failure on line 6 in guide/en/concept/immutability.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/concept/immutability.md#L6

[Microsoft.Contractions] Use 'can't' instead of 'cannot'.
Raw output
{"message": "[Microsoft.Contractions] Use 'can't' instead of 'cannot'.", "location": {"path": "guide/en/concept/immutability.md", "range": {"start": {"line": 6, "column": 9}}}, "severity": "ERROR"}

Check warning on line 6 in guide/en/concept/immutability.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/concept/immutability.md#L6

[Microsoft.Adverbs] Remove 'silently' if it's not important to the meaning of the statement.
Raw output
{"message": "[Microsoft.Adverbs] Remove 'silently' if it's not important to the meaning of the statement.", "location": {"path": "guide/en/concept/immutability.md", "range": {"start": {"line": 6, "column": 16}}}, "severity": "WARNING"}

## Mutable pitfalls (what we avoid)

Check warning on line 8 in guide/en/concept/immutability.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/concept/immutability.md#L8

[Microsoft.We] Try to avoid using first-person plural like 'we'.
Raw output
{"message": "[Microsoft.We] Try to avoid using first-person plural like 'we'.", "location": {"path": "guide/en/concept/immutability.md", "range": {"start": {"line": 8, "column": 27}}}, "severity": "WARNING"}

```php
// A shared base query built once and reused:
$base = Post::find()->where(['status' => Post::STATUS_PUBLISHED]);

// Somewhere deep in the code we only need one post:
$one = $base->limit(1)->one(); // mutates the underlying builder (sticky limit!)

// Later we reuse the same $base expecting a full list:
$list = $base->orderBy(['created_at' => SORT_DESC])->all();
// Oops: still limited to 1 because the previous limit(1) modified $base.
```

## Creating an immutable object in PHP

Check warning on line 22 in guide/en/concept/immutability.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/concept/immutability.md#L22

[Microsoft.HeadingAcronyms] Avoid using acronyms in a title or heading.
Raw output
{"message": "[Microsoft.HeadingAcronyms] Avoid using acronyms in a title or heading.", "location": {"path": "guide/en/concept/immutability.md", "range": {"start": {"line": 22, "column": 36}}}, "severity": "WARNING"}

There is no direct way to modify an instance, but you can use clone to create a new instance with the desired changes.
That is what `with*` methods do.

Check failure on line 25 in guide/en/concept/immutability.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/concept/immutability.md#L25

[Microsoft.Contractions] Use 'that's' instead of 'That is'.
Raw output
{"message": "[Microsoft.Contractions] Use 'that's' instead of 'That is'.", "location": {"path": "guide/en/concept/immutability.md", "range": {"start": {"line": 25, "column": 1}}}, "severity": "ERROR"}

```php
final class Money
{
public function __construct(
private int $amount,
private string $currency,
) {
$this->validateAmount($amount);
$this->validateCurrency($currency);
}

private function validateAmount(string $amount)
{
if ($amount < 0) {
throw new InvalidArgumentException('Amount must be positive.');
}
}

private function validateCurrency(string $currency)
{
if (!in_array($currency, ['USD', 'EUR'])) {
throw new InvalidArgumentException('Invalid currency. Only USD and EUR are supported.');
}
}

public function withAmount(int $amount): self
{
$this->validateAmount($amount);

if ($amount === $this->amount) {
return $this;
}

$clone = clone $this;
$clone->amount = $amount;
return $clone;
}

public function withCurrency(string $currency): self
{
$this->validateCurrency($currency);

if ($currency === $this->currency) {
return $this;
}

$clone = clone $this;
$clone->currency = $currency;
return $clone;
}

public function amount(): int
{
return $this->amount;
}

public function currency(): string
{
return $this->currency;
}

public function add(self $money): self
{
if ($money->currency !== $this->currency) {
throw new InvalidArgumentException('Currency mismatch. Cannot add money of different currency.');
}
return $this->withAmount($this->amount + $money->amount);
}
}

$price = new Money(1000, 'USD');
$discounted = $price->withAmount(800);
// $price is still 1000 USD, $discounted is 800 USD
```

- We mark the class `final` to prevent subclass mutations; alternatively, design for extension carefully.

Check warning on line 102 in guide/en/concept/immutability.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/concept/immutability.md#L102

[Microsoft.We] Try to avoid using first-person plural like 'We'.
Raw output
{"message": "[Microsoft.We] Try to avoid using first-person plural like 'We'.", "location": {"path": "guide/en/concept/immutability.md", "range": {"start": {"line": 102, "column": 3}}}, "severity": "WARNING"}

Check warning on line 102 in guide/en/concept/immutability.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/concept/immutability.md#L102

[Microsoft.Adverbs] Remove 'carefully' if it's not important to the meaning of the statement.
Raw output
{"message": "[Microsoft.Adverbs] Remove 'carefully' if it's not important to the meaning of the statement.", "location": {"path": "guide/en/concept/immutability.md", "range": {"start": {"line": 102, "column": 96}}}, "severity": "WARNING"}
- Validate in the constructor and `with*` methods so every instance is always valid.

> [!TIP]

Check notice on line 105 in guide/en/concept/immutability.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/concept/immutability.md#L105

[Microsoft.Acronyms] 'TIP' has no definition.
Raw output
{"message": "[Microsoft.Acronyms] 'TIP' has no definition.", "location": {"path": "guide/en/concept/immutability.md", "range": {"start": {"line": 105, "column": 5}}}, "severity": "INFO"}
> If you define a simple DTO, you can use modern PHP `readonly` and leave properties `public`. The `readonly` keyword

Check notice on line 106 in guide/en/concept/immutability.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/concept/immutability.md#L106

[Microsoft.Acronyms] 'DTO' has no definition.
Raw output
{"message": "[Microsoft.Acronyms] 'DTO' has no definition.", "location": {"path": "guide/en/concept/immutability.md", "range": {"start": {"line": 106, "column": 26}}}, "severity": "INFO"}
> would ensure that the properties cannot be modified after the object is created.

Check notice on line 107 in guide/en/concept/immutability.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/concept/immutability.md#L107

[Microsoft.Vocab] Verify your use of 'ensure' with the A-Z word list.
Raw output
{"message": "[Microsoft.Vocab] Verify your use of 'ensure' with the A-Z word list.", "location": {"path": "guide/en/concept/immutability.md", "range": {"start": {"line": 107, "column": 9}}}, "severity": "INFO"}

Check failure on line 107 in guide/en/concept/immutability.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/concept/immutability.md#L107

[Microsoft.Contractions] Use 'can't' instead of 'cannot'.
Raw output
{"message": "[Microsoft.Contractions] Use 'can't' instead of 'cannot'.", "location": {"path": "guide/en/concept/immutability.md", "range": {"start": {"line": 107, "column": 36}}}, "severity": "ERROR"}

Check notice on line 107 in guide/en/concept/immutability.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/concept/immutability.md#L107

[Microsoft.Passive] 'be modified' looks like passive voice.
Raw output
{"message": "[Microsoft.Passive] 'be modified' looks like passive voice.", "location": {"path": "guide/en/concept/immutability.md", "range": {"start": {"line": 107, "column": 43}}}, "severity": "INFO"}

Check notice on line 107 in guide/en/concept/immutability.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/concept/immutability.md#L107

[Microsoft.Passive] 'is created' looks like passive voice.
Raw output
{"message": "[Microsoft.Passive] 'is created' looks like passive voice.", "location": {"path": "guide/en/concept/immutability.md", "range": {"start": {"line": 107, "column": 72}}}, "severity": "INFO"}

## Using clone (and why it is cheap)

Check failure on line 109 in guide/en/concept/immutability.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/concept/immutability.md#L109

[Microsoft.Contractions] Use 'it's' instead of 'it is'.
Raw output
{"message": "[Microsoft.Contractions] Use 'it's' instead of 'it is'.", "location": {"path": "guide/en/concept/immutability.md", "range": {"start": {"line": 109, "column": 25}}}, "severity": "ERROR"}

PHP's clone performs a shallow copy of the object. For immutable value objects that contain only scalars
or other immutable objects, shallow cloning is enough and fast. In modern PHP, cloning small value objects is
inexpensive in both time and memory.

If your object holds mutable sub-objects that must also be copied, implement `__clone` to deep-clone them:

Check notice on line 115 in guide/en/concept/immutability.md

View workflow job for this annotation

GitHub Actions / vale

[vale] guide/en/concept/immutability.md#L115

[Microsoft.Passive] 'be copied' looks like passive voice.
Raw output
{"message": "[Microsoft.Passive] 'be copied' looks like passive voice.", "location": {"path": "guide/en/concept/immutability.md", "range": {"start": {"line": 115, "column": 57}}}, "severity": "INFO"}

```php
final class Order
{
public function __construct(
private Money $total
) {}

public function total(): Money
{
return $this->total;
}

public function __clone(): void
{
// Money is immutable in our example, so a deep clone is not required.
// If it were mutable, you could do: $this->total = clone $this->total;
}

public function withTotal(Money $total): self
{
$clone = clone $this;
$clone->total = $total;
return $clone;
}
}
```

## Usage style

- Build a value object once and pass it around. If you need a change, use a `with*` method that returns a new instance.
- Prefer scalar/immutable fields inside immutable objects; if a field can mutate, isolate it and deep-clone in `__clone`
when needed.

Immutability aligns well with Yii's preference for predictable, side-effect-free code and makes services, caching,
and configuration more robust.
Loading