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
7 changes: 7 additions & 0 deletions docs/en/appendices/6-0-migration-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@ Some properties have also been renamed to better reflect their purpose. These ar

## New Features

### ORM

- Entities can now map fields to real class properties, including typed
properties and PHP 8.4 asymmetric visibility/property hooks. When changing
declared fields inside entity methods, continue to use `set()`/`patch()` if
you need dirty tracking and related entity bookkeeping.

### Router

- Attribute routing is now available via `RouteBuilder::connectAttributes()` and the
Expand Down
4 changes: 2 additions & 2 deletions docs/en/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ use Cake\ORM\Entity;

class Article extends Entity
{
protected array $_accessible = [
protected array $patchable = [
'title' => true,
'slug' => true,
'body' => true,
Expand Down Expand Up @@ -627,7 +627,7 @@ declare(strict_types=1);
// ✓ Mass assignment (protected fields)

$article = $this->Articles->newEntity($data);
// Only $_accessible fields can be set
// Only patchable fields can be set
```

```bash [Code Generation]
Expand Down
4 changes: 2 additions & 2 deletions docs/en/orm/behaviors/translate.md
Original file line number Diff line number Diff line change
Expand Up @@ -544,8 +544,8 @@ $this->Articles->save($article);
```

This will result in your article, the french and spanish translations all being
persisted. You'll need to remember to add `_translations` into the
`$_accessible` fields of your entity as well.
persisted. You'll need to remember to add `_translations` to the `patchable`
fields of your entity as well.

### Validating Translated Entities

Expand Down
125 changes: 104 additions & 21 deletions docs/en/orm/entities.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: "Entities"
description: "Manage CakePHP entities: access data, implement accessors/mutators, handle mass assignment, virtual fields, and custom entity logic."
description: "Manage CakePHP entities: access data, use typed properties and property hooks, handle mass assignment, virtual fields, and custom entity logic."
---

# Entities
Expand All @@ -11,6 +11,8 @@ While [Table Objects](../orm/table-objects) represent and provide access to a co
objects, entities represent individual rows or domain objects in your
application. Entities contain methods to manipulate and
access the data they contain. Fields can also be accessed as properties on the object.
In CakePHP 6.0 those fields can be stored as dynamic fields or mapped onto
real class properties in your entity class.

Entities are created for you each time you iterate the query instance returned
by `find()` of a table object or when you call `all()` or `first()` method
Expand Down Expand Up @@ -171,6 +173,87 @@ $article->has('links'); // true
$article->hasValue('links'); // false
```

### Declaring Concrete Properties

In CakePHP 6.0 entity fields can be mapped onto real class properties instead
of being stored only as dynamic fields. This lets you use native PHP types in
your entities while continuing to use CakePHP's entity features such as
`get()`, `set()`, `patch()`, dirty tracking, original values, and mass
assignment.

```php
namespace App\Model\Entity;

use Cake\ORM\Entity;

class User extends Entity
{
public protected(set) int $id;
public protected(set) string $email;
public protected(set) ?string $first_name;
public protected(set) ?string $last_name;
public protected(set) bool $is_active;
}
```

Using `public protected(set)` lets outside code read the property directly
while writes still go through the entity API. If you use `protected`
properties instead, both reads and writes from outside the entity continue to
use CakePHP's magic property handling.

Properties can also use PHP property hooks:

```php
namespace App\Model\Entity;

use Cake\ORM\Entity;

class User extends Entity
{
public protected(set) ?string $password {
set (?string $value) {
$this->password = $value === null
? null
: password_hash($value, PASSWORD_DEFAULT);
}
}
}
```

Property `set` hooks are bypassed when the ORM hydrates database rows with
setters disabled, so persisted values are assigned without being transformed a
second time.

You can also use `get` hooks to define virtual computed properties without
backing storage:

```php
namespace App\Model\Entity;

use Cake\ORM\Entity;

class User extends Entity
{
public protected(set) string $first_name;
public protected(set) string $last_name;

public string $full_name {
get => trim($this->first_name . ' ' . $this->last_name);
}
}
```

> [!WARNING]
> When changing declared fields from inside entity methods, use `$this->set()`
> or `$this->patch()` instead of assigning `$this->field` directly. Direct
> assignment inside the entity bypasses CakePHP's dirty tracking and original
> value bookkeeping.

> [!NOTE]
> If a database column name conflicts with a built-in `Entity` property such as
> `patchable`, `dirty`, or `errors`, keep that column as a dynamic field instead
> of declaring it as a concrete property.

If you often partially load entities you should enable strict-property access
behavior to ensure you're not using properties that haven't been loaded. On
a per-entity basis you can enable this behavior:
Expand All @@ -195,8 +278,9 @@ Accessors let you customize how fields are read. They use the convention of
words are joined together to a single word with the first letter of each word
capitalized) of the field name.

They receive the basic value stored in the `_fields` array as their only
argument. For example:
They receive the basic value stored for the field as their only argument,
whether that value comes from a concrete property or a dynamic field. For
example:

```php
namespace App\Model\Entity;
Expand Down Expand Up @@ -402,9 +486,9 @@ into an entity allows the user to modify any and all columns. When using
anonymous entity classes or creating the entity class with the [Bake Console](../bake)
CakePHP does not protect against mass-assignment.

The `_accessible` property allows you to provide a map of fields and
whether or not they can be mass-assigned. The values `true` and `false`
indicate whether a field can or cannot be mass-assigned:
The `patchable` property allows you to provide a map of fields and whether or
not they can be mass-assigned. The values `true` and `false` indicate whether
a field can or cannot be mass-assigned:

```php
namespace App\Model\Entity;
Expand All @@ -413,7 +497,7 @@ use Cake\ORM\Entity;

class Article extends Entity
{
protected array $_accessible = [
protected array $patchable = [
'title' => true,
'body' => true,
];
Expand All @@ -430,7 +514,7 @@ use Cake\ORM\Entity;

class Article extends Entity
{
protected array $_accessible = [
protected array $patchable = [
'title' => true,
'body' => true,
'*' => false,
Expand All @@ -452,26 +536,25 @@ use App\Model\Entity\Article;
$article = new Article(['id' => 1, 'title' => 'Foo'], ['guard' => false]);
```

### Modifying the Guarded Fields at Runtime
### Modifying the Patchable Fields at Runtime

You can modify the list of guarded fields at runtime using the `setAccess()`
method:
You can modify the list of patchable fields at runtime using the
`setPatchable()` method:

```php
// Make user_id accessible.
$article->setAccess('user_id', true);
// Make user_id patchable.
$article->setPatchable('user_id', true);

// Make title guarded.
$article->setAccess('title', false);
$article->setPatchable('title', false);
```

> [!NOTE]
> Modifying accessible fields affects only the instance the method is called
> on.
> Modifying patchable fields affects only the instance the method is called on.

When using the `newEntity()` and `patchEntity()` methods in the `Table`
objects you can customize mass assignment protection with options. Please refer
to the [Changing Accessible Fields](../orm/saving-data#changing-accessible-fields) section for more information.
to the [Changing Patchable Fields](../orm/saving-data#changing-patchable-fields) section for more information.

### Bypassing Field Guarding

Expand All @@ -482,8 +565,8 @@ fields:
$article->patch($fields, ['guard' => false]);
```

By setting the `guard` option to `false`, you can ignore the accessible
field list for a single call to `patch()`.
By setting the `guard` option to `false`, you can ignore the patchable field
list for a single call to `patch()`.

### Checking if an Entity was Persisted

Expand Down Expand Up @@ -618,7 +701,7 @@ use Cake\ORM\Entity;

class User extends Entity
{
protected array $_virtual = ['full_name'];
protected array $virtual = ['full_name'];
}
```

Expand All @@ -642,7 +725,7 @@ use Cake\ORM\Entity;

class User extends Entity
{
protected array $_hidden = ['password'];
protected array $hidden = ['password'];
}
```

Expand Down
6 changes: 3 additions & 3 deletions docs/en/orm/retrieving-data-and-resultsets.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,10 +341,10 @@ list finds.

```php
// In your Authors Entity create a virtual field to be used as the displayField:
protected function _getLabel()
protected function _getLabel(): string
{
return $this->_fields['first_name'] . ' ' . $this->_fields['last_name']
. ' / ' . __('User ID %s', $this->_fields['user_id']);
return $this->first_name . ' ' . $this->last_name
. ' / ' . __('User ID %s', $this->user_id);
}
```

Expand Down
30 changes: 15 additions & 15 deletions docs/en/orm/saving-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ $entity = $articles->newEntity($this->request->getData());
```

> [!NOTE]
> If you are using newEntity() and the resulting entities are missing some or
> all the data they were passed, double check that the columns you want to
> set are listed in the `$_accessible` property of your entity. See [Entities Mass Assignment](../orm/entities#entities-mass-assignment).
> If you are using `newEntity()` and the resulting entities are missing some or
> all the data they were passed, double check that the columns you want to set
> are listed in the `patchable` property of your entity. See [Entities Mass Assignment](../orm/entities#entities-mass-assignment).

The request data should follow the structure of your entities. For example if
you have an article, which belonged to a user, and had many comments, your
Expand Down Expand Up @@ -430,12 +430,12 @@ $articles->saveMany($entities);
$articles->saveManyOrFail($entities);
```

### Changing Accessible Fields
### Changing Patchable Fields

It's also possible to allow `newEntity()` to write into non accessible fields.
For example, `id` is usually absent from the `_accessible` property. In
such case, you can use the `accessibleFields` option. It could be useful to
keep ids of associated entities:
It's also possible to allow `newEntity()` to write into non-patchable fields.
For example, `id` is usually absent from the `patchable` property. In that
case, you can use the `patchableFields` option. It can be useful to keep ids
of associated entities:

```php
// In a controller
Expand All @@ -446,7 +446,7 @@ $entity = $articles->newEntity($this->request->getData(), [
'Tags', 'Comments' => [
'associated' => [
'Users' => [
'accessibleFields' => ['id' => true],
'patchableFields' => ['id' => true],
],
],
],
Expand All @@ -458,9 +458,9 @@ The above will keep the association unchanged between Comments and Users for the
concerned entity.

> [!NOTE]
> If you are using newEntity() and the resulting entities are missing some or
> all the data they were passed, double check that the columns you want to
> set are listed in the `$_accessible` property of your entity. See
> If you are using `newEntity()` and the resulting entities are missing some or
> all the data they were passed, double check that the columns you want to set
> are listed in the `patchable` property of your entity. See
> [Entities Mass Assignment](../orm/entities#entities-mass-assignment).

### Merging Request Data Into Entities
Expand Down Expand Up @@ -553,14 +553,14 @@ The same can be said about hasMany and belongsToMany associations, with
an important caveat:

> [!NOTE]
> For belongsToMany associations, ensure the relevant entity has
> a property accessible for the associated entity.
> For belongsToMany associations, ensure the relevant entity has a patchable
> property for the associated entity.

If a Product belongsToMany Tag:

```php
// in the Product Entity
protected array $_accessible = [
protected array $patchable = [
// .. other properties
'tags' => true,
];
Expand Down
4 changes: 2 additions & 2 deletions docs/en/tutorials-and-examples/cms/articles-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ use Cake\ORM\Entity;

class Article extends Entity
{
protected array $_accessible = [
protected array $patchable = [
'user_id' => true,
'title' => true,
'slug' => true,
Expand All @@ -79,7 +79,7 @@ class Article extends Entity
}
```

Right now, our entity is quite slim; we've only set up the `_accessible`
Right now, our entity is quite slim; we've only set up the `patchable`
property, which controls how properties can be modified by
[Entities Mass Assignment](../../orm/entities#entities-mass-assignment).

Expand Down
4 changes: 2 additions & 2 deletions docs/en/tutorials-and-examples/cms/authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ public function edit($slug)
if ($this->request->is(['post', 'put'])) {
$this->Articles->patchEntity($article, $this->request->getData(), [
// Added: Disable modification of user_id.
'accessibleFields' => ['user_id' => false],
'patchableFields' => ['user_id' => false],
]);
if ($this->Articles->save($article)) {
$this->Flash->success(__('Your article has been updated.'));
Expand All @@ -264,7 +264,7 @@ public function edit($slug)
```

Here we're modifying which properties can be mass-assigned, via the options
for `patchEntity()`. See the [Changing Accessible Fields](../../orm/saving-data#changing-accessible-fields) section for
for `patchEntity()`. See the [Changing Patchable Fields](../../orm/saving-data#changing-patchable-fields) section for
more information. Remember to remove the `user_id` control from
**templates/Articles/edit.php** as we no longer need it.

Expand Down
Loading