Skip to content

Commit 3fb3f2a

Browse files
authored
Add doc for MagicPropertiesTrait and MagicRelationsTrait (#467)
1 parent 6ee0423 commit 3fb3f2a

6 files changed

Lines changed: 222 additions & 19 deletions

File tree

docs/create-model.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,8 @@ final class User extends ActiveRecord
214214
You can use `$user->id`, `$user->username`, `$user->email` to access the properties as with dynamic properties.
215215

216216
Notes:
217-
- It needs to use the `MagicPropertiesTrait` to enable magic properties;
218-
- Compared to dynamic properties, they're stored in the `private array $properties` property;
217+
- It needs to use the [MagicPropertiesTrait](traits/magic-properties.md) to enable magic properties;
218+
- Compared to dynamic properties, they're stored in the `private array $propertyValues` property;
219219
- ✔️ It allows accessing relations as properties;
220220
- ❌ It doesn't use strict typing and can be a reason of hard-to-detect errors;
221221
- ❌ It is slower than explicitly defined properties, it is not optimized by PHP opcache and uses more memory.

docs/define-relations.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@ $ordersQuery = $user->relationQuery('orders');
4242

4343
### Using `MagicRelationsTrait`
4444

45-
Alternatively, you can use `MagicRelationsTrait` trait to define relations in the Active Record model. This trait allows
46-
you to define relation methods directly in the model without overriding `relationQuery()` method. The relation
47-
methods should have a specific naming convention to be recognized by the trait. The method names should have prefix
48-
`get` and suffix `Query` and returns an object implementing `ActiveQueryInterface` interface.
45+
Alternatively, you can use [MagicRelationsTrait](traits/magic-relations.md) trait to define relations in the Active Record model.
46+
This trait allows you to define relation methods directly in the model without overriding `relationQuery()` method.
47+
The relation methods should have a specific naming convention to be recognized by the trait. The method names should
48+
have prefix `get` and suffix `Query` and returns an object implementing `ActiveQueryInterface` interface.
4949

5050
```php
51-
use Yiisoft\ActiveRecord\ActiveRecord;
5251
use Yiisoft\ActiveRecord\ActiveQueryInterface;
52+
use Yiisoft\ActiveRecord\ActiveRecord;
5353
use Yiisoft\ActiveRecord\MagicRelationsTrait;
5454

5555
final class User extends ActiveRecord

docs/traits/magic-properties.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# MagicPropertiesTrait
2+
3+
`MagicPropertiesTrait` allows to use magic getters and setters to access model properties and relations.
4+
It stores properties in the `private array $propertyValues` property and provides magic methods for accessing them.
5+
6+
It also allows to call getter and setter methods as a property if they are defined in the model class
7+
(e.g. `getFullName()` and `setFullName($fullName)` for `fullName` property).
8+
9+
> [!NOTE]
10+
> This trait is not required when using private, protected, public or dynamic properties.
11+
12+
> [!IMPORTANT]
13+
> - ✔️ It allows accessing relations as properties;
14+
> - ❌ It doesn't use strict typing and can be a reason of hard-to-detect errors;
15+
> - ❌ It is slower than explicitly defined properties, it is not optimized by PHP opcache and uses more memory.
16+
> Sometimes it can be 100 times slower than explicitly defined properties;
17+
18+
## Methods
19+
20+
The following methods are provided by the `MagicPropertiesTrait`:
21+
22+
- `__get()` retrieves the value of the specified property or relation using magic getter;
23+
- `__set()` sets the value of the specified property or relation using magic setter;
24+
- `__isset()` checks if the specified property or relation exists and is not null using magic isset;
25+
- `__unset()` unsets or sets to null the specified property or relation using magic unset;
26+
- `hasRelationQuery()` checks if the specified relation query exists;
27+
- `isProperty()` checks if the specified property exists;
28+
- `canGetProperty()` checks if the specified property can be gotten;
29+
- `canSetProperty()` checks if the specified property can be set.
30+
31+
## Usage
32+
33+
```php
34+
use Yiisoft\ActiveRecord\ActiveRecord;
35+
use Yiisoft\ActiveRecord\Trait\MagicPropertiesTrait;
36+
37+
/**
38+
* Entity User.
39+
*
40+
* @property int $id
41+
* @property string $firstName
42+
* @property string $lastName
43+
* @property string $fullName
44+
*
45+
* The properties in PHPDoc are optional and used by static analysis and by IDEs for autocompletion, type hinting,
46+
* code generation, and inspection tools. This doesn't affect code execution.
47+
**/
48+
final class User extends ActiveRecord
49+
{
50+
use MagicPropertiesTrait;
51+
52+
public function getProfileQuery(): ActiveQueryInterface
53+
{
54+
return $this->hasOne(Profile::class, ['id' => 'profile_id']);
55+
}
56+
57+
public function getFullName(): string
58+
{
59+
return $this->firstName . ' ' . $this->lastName;
60+
}
61+
62+
public function setFullName(string $fullName): void
63+
{
64+
[$this->firstName, $this->lastName] = explode(' ', $fullName, 2);
65+
}
66+
}
67+
68+
$user = new User();
69+
$user->firstName = 'John'; // Set the property value using magic setter
70+
$user->fullName = 'John Smith'; // Set the property values using setter method
71+
echo $user->firstName; // Get the property value using magic getter
72+
echo $user->fullName; // Get the property value using getter method
73+
unset($user->firstName); // Unset the property or set it to null using magic unset
74+
isset($user->firstName); // Check if the property exists and is not null using magic isset
75+
76+
$user->hasRelationQuery('profile') // Check if the relation query exists
77+
$user->isProperty('fullName') // Check if the property exists (can be read or set)
78+
$user->canGetProperty('fullName') // Check if the property can be read
79+
$user->canSetProperty('fullName'); // Check if the property can be set
80+
```
81+
82+
Usually `MagicPropertiesTrait` and [MagicRelationsTrait](magic-relations.md) are used together to provide full magic functionality
83+
for properties and relations.
84+
85+
```php
86+
final class User extends ActiveRecord
87+
{
88+
use MagicPropertiesTrait;
89+
use MagicRelationsTrait;
90+
}
91+
```
92+
93+
See more how to [Create Active Record Model](../create-model.md).
94+
95+
Back to [Extending Functionality With Traits](traits.md).

docs/traits/magic-relations.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# MagicRelationsTrait
2+
3+
`MagicRelationsTrait` allows using methods with the prefix `get` and suffix `Query` to define relations
4+
in an Active Record model. For example, a method named `getOrdersQuery()` can be used to define a relation named `orders`.
5+
6+
## Methods
7+
8+
The following method is provided by the `MagicRelationsTrait`:
9+
10+
- `relationNames()` returns names of all relations defined in the Active Record class using getter methods with
11+
`get` prefix and `Query` suffix.
12+
13+
## Usage
14+
15+
```php
16+
use Yiisoft\ActiveRecord\ActiveQueryInterface;
17+
use Yiisoft\ActiveRecord\ActiveRecord;
18+
use Yiisoft\ActiveRecord\MagicRelationsTrait;
19+
20+
final class User extends ActiveRecord
21+
{
22+
use MagicRelationsTrait;
23+
24+
public function getProfileQuery(): ActiveQueryInterface
25+
{
26+
return $this->hasOne(Profile::class, ['id' => 'profile_id']);
27+
}
28+
29+
public function getOrdersQuery(): ActiveQueryInterface
30+
{
31+
return $this->hasMany(Order::class, ['user_id' => 'id']);
32+
}
33+
}
34+
35+
$user = new User();
36+
$orders = $user->relation('orders'); // Access the "orders" relation defined by the getOrdersQuery() method
37+
$user->relationNames(); // Returns ['profile', 'orders']
38+
```
39+
40+
See more how to [Define Relations](../define-relations.md).
41+
42+
Back to [Extending Functionality With Traits](traits.md).

docs/traits/traits.md

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,72 @@ These traits can be included in your model classes to add specific behaviors or
1010
- [CustomTableNameTrait](custom-table-name.md) allows using a custom table name for a model;
1111
- [EventsTrait](events.md) allows using events and handlers for a model;
1212
- [FactoryTrait](factory.md) allows creating models and relations using [yiisoft/factory](https://github.com/yiisoft/factory);
13-
- `MagicPropertiesTrait` stores properties in a private property and provides magic getters
13+
- [MagicPropertiesTrait](magic-properties.md) stores properties in a private property and provides magic getters
1414
and setters for accessing the model properties and relations;
15-
- `MagicRelationsTrait` allows using methods with prefix `get` and suffix `Query` to define
15+
- [MagicRelationsTrait](magic-relations.md) allows using methods with prefix `get` and suffix `Query` to define
1616
relations (e.g. `getOrdersQuery()` for `orders` relation);
1717
- [PrivatePropertiesTrait](private-properties.md) allows using [private properties](../create-model.md#private-properties)
1818
in a model;
1919
- [RepositoryTrait](repository.md) provides methods to interact with a model as a repository.
2020

21+
All traits are optional and can be used as needed. They can be combined to create models with the desired functionality.
22+
23+
For example, to create an Active Record class that supports array access and can be converted to an array:
24+
25+
```php
26+
use Yiisoft\ActiveRecord\ActiveRecord;
27+
use Yiisoft\ActiveRecord\Trait\ArrayableTrait;
28+
use Yiisoft\ActiveRecord\Trait\ArrayAccessTrait;
29+
use Yiisoft\ActiveRecord\Trait\ArrayIteratorTrait;
30+
31+
class ArrayActiveRecord extends ActiveRecord
32+
{
33+
use ArrayableTrait;
34+
use ArrayAccessTrait;
35+
use ArrayIteratorTrait;
36+
}
37+
```
38+
39+
Then you can create your model class by extending `ArrayActiveRecord`:
40+
41+
```php
42+
final class User extends ArrayActiveRecord
43+
{
44+
protected int $id;
45+
protected string $username;
46+
protected string $email;
47+
protected string $status = 'active';
48+
}
49+
```
50+
51+
Another example, to create an Active Record class that uses magic properties and relations:
52+
53+
```php
54+
use Yiisoft\ActiveRecord\ActiveRecord;
55+
use Yiisoft\ActiveRecord\Trait\MagicPropertiesTrait;
56+
use Yiisoft\ActiveRecord\Trait\MagicRelationsTrait;
57+
58+
class MagicActiveRecord extends ActiveRecord
59+
{
60+
use MagicPropertiesTrait;
61+
use MagicRelationsTrait;
62+
}
63+
```
64+
65+
Then you can create your model class by extending `MagicActiveRecord`:
66+
67+
```php
68+
/**
69+
* Entity User.
70+
*
71+
* @property int $id
72+
* @property string $username
73+
* @property string $email
74+
* @property string $status
75+
*/
76+
final class User extends MagicActiveRecord
77+
{
78+
}
79+
```
80+
2181
Back to [README](../../README.md)

src/Trait/MagicPropertiesTrait.php

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,6 @@ trait MagicPropertiesTrait
4545
/** @psalm-var array<string, mixed> $propertyValues */
4646
private array $propertyValues = [];
4747

48-
/**
49-
* Returns a value indicating whether the record has a relation query with the specified name.
50-
*
51-
* @param string $name The name of the relation query.
52-
*/
53-
public function hasRelationQuery(string $name): bool
54-
{
55-
return method_exists($this, "get{$name}Query");
56-
}
57-
5848
/**
5949
* PHP getter magic method.
6050
* This method is overridden so that values and related objects can be accessed like properties.
@@ -165,6 +155,16 @@ public function hasProperty(string $name): bool
165155
return isset($this->propertyValues[$name]) || in_array($name, $this->propertyNames(), true);
166156
}
167157

158+
/**
159+
* Returns a value indicating whether the record has a relation query with the specified name.
160+
*
161+
* @param string $name The name of the relation query.
162+
*/
163+
public function hasRelationQuery(string $name): bool
164+
{
165+
return method_exists($this, "get{$name}Query");
166+
}
167+
168168
public function set(string $propertyName, mixed $value): void
169169
{
170170
if ($this->hasProperty($propertyName)) {
@@ -200,6 +200,9 @@ public function isProperty(string $name, bool $checkVars = true): bool
200200
|| $this->hasProperty($name);
201201
}
202202

203+
/**
204+
* Returns a value indicating whether a property can be read.
205+
*/
203206
public function canGetProperty(string $name, bool $checkVars = true): bool
204207
{
205208
return method_exists($this, "get$name")
@@ -208,6 +211,9 @@ public function canGetProperty(string $name, bool $checkVars = true): bool
208211
|| $this->hasProperty($name);
209212
}
210213

214+
/**
215+
* Returns a value indicating whether a property can be set.
216+
*/
211217
public function canSetProperty(string $name, bool $checkVars = true): bool
212218
{
213219
return method_exists($this, "set$name")

0 commit comments

Comments
 (0)