Skip to content

Commit 8a113cf

Browse files
committed
Remove magic synchronization for relations (issue #19788).
1 parent c8c0ea9 commit 8a113cf

File tree

11 files changed

+44
-316
lines changed

11 files changed

+44
-316
lines changed

framework/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Yii Framework 2 Change Log
44
2.0.49 under development
55
------------------------
66

7+
- Chg #19788: Removed all (often non-functional) attempts of Yii2 to automatically synchronize ActiveRecord relations with corresponding foreign key values. The new guarantee provided by Yii2 is: once set ActiveRecord relations are never automatically or silently changed/unset by the engine (PowerGamer1)
78
- Bug #19899: Fixed `GridView` in some cases calling `Model::generateAttributeLabel()` to generate label values that are never used (PowerGamer1)
89
- Bug #9899: Fix caching a MSSQL query with BLOB data type (terabytesoftw)
910
- Bug #16208: Fix `yii\log\FileTarget` to not export empty messages (terabytesoftw)

framework/UPGRADE.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,29 @@ Upgrade from Yii 2.0.48
6262
* The function signature for `yii\console\Controller::select()` and `yii\helpers\BaseConsole::select()` have changed.
6363
They now have an additional `$default = null` parameter. In case those methods are overwritten you will need to
6464
update your child classes accordingly.
65+
* The engine no longer attempts to provide an automatic synchronization between ActiveRecord relations and corresponding foreign keys.
66+
Such synchronization never worked in many cases, came with ActiveRecord performance and memory costs and in some cases is impossible to achieve (see https://github.com/yiisoft/yii2/issues/19788 for details).
67+
The new guarantee provided by Yii2 is: once set ActiveRecord relations are never automatically or silently changed/unset by the engine.
68+
All places in existing code that use already loaded relation after it is expected to change need to manually unset such relation. For example, in the code below:
69+
```php
70+
$project = Project::findOne(123);
71+
$oldManager = $project->manager;
72+
$project->load(Yii::$app->getRequest()->post()); // $project->manager_id may change here.
73+
$project->update();
74+
$newManager = $project->manager;
75+
// Notify $oldManager and $newManager about the reassignment by email.
76+
```
77+
the access to `$project->manager` after update should be preceded by unsetting that relation:
78+
```PHP
79+
// ... (same as above).
80+
$project->update();
81+
unset($project->manager);
82+
$newManager = $project->manager;
83+
// Notify $oldManager and $newManager about the reassignment by email.
84+
```
85+
Another notable example is using `ActiveRecord::refresh()`. If the refreshed model had relations loaded before the call to `refresh()`
86+
and these relations are expected to change, unset them explicitly with `unset()` before using again.
87+
6588

6689
Upgrade from Yii 2.0.46
6790
-----------------------

framework/db/BaseActiveRecord.php

Lines changed: 0 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,6 @@ public function __get($name)
295295
}
296296
$value = parent::__get($name);
297297
if ($value instanceof ActiveQueryInterface) {
298-
$this->setRelationDependencies($name, $value);
299298
return $this->_related[$name] = $value->findFor($name, $this);
300299
}
301300

@@ -311,12 +310,6 @@ public function __get($name)
311310
public function __set($name, $value)
312311
{
313312
if ($this->hasAttribute($name)) {
314-
if (
315-
!empty($this->_relationsDependencies[$name])
316-
&& (!array_key_exists($name, $this->_attributes) || $this->_attributes[$name] !== $value)
317-
) {
318-
$this->resetDependentRelations($name);
319-
}
320313
$this->_attributes[$name] = $value;
321314
} else {
322315
parent::__set($name, $value);
@@ -350,9 +343,6 @@ public function __unset($name)
350343
{
351344
if ($this->hasAttribute($name)) {
352345
unset($this->_attributes[$name]);
353-
if (!empty($this->_relationsDependencies[$name])) {
354-
$this->resetDependentRelations($name);
355-
}
356346
} elseif (array_key_exists($name, $this->_related)) {
357347
unset($this->_related[$name]);
358348
} elseif ($this->getRelation($name, false) === null) {
@@ -460,10 +450,6 @@ protected function createRelationQuery($class, $link, $multiple)
460450
*/
461451
public function populateRelation($name, $records)
462452
{
463-
foreach ($this->_relationsDependencies as &$relationNames) {
464-
unset($relationNames[$name]);
465-
}
466-
467453
$this->_related[$name] = $records;
468454
}
469455

@@ -521,12 +507,6 @@ public function getAttribute($name)
521507
public function setAttribute($name, $value)
522508
{
523509
if ($this->hasAttribute($name)) {
524-
if (
525-
!empty($this->_relationsDependencies[$name])
526-
&& (!array_key_exists($name, $this->_attributes) || $this->_attributes[$name] !== $value)
527-
) {
528-
$this->resetDependentRelations($name);
529-
}
530510
$this->_attributes[$name] = $value;
531511
} else {
532512
throw new InvalidArgumentException(get_class($this) . ' has no attribute named "' . $name . '".');
@@ -1081,8 +1061,6 @@ protected function refreshInternal($record)
10811061
$this->_attributes[$name] = isset($record->_attributes[$name]) ? $record->_attributes[$name] : null;
10821062
}
10831063
$this->_oldAttributes = $record->_oldAttributes;
1084-
$this->_related = [];
1085-
$this->_relationsDependencies = [];
10861064
$this->afterRefresh();
10871065

10881066
return true;
@@ -1196,8 +1174,6 @@ public static function populateRecord($record, $row)
11961174
}
11971175
}
11981176
$record->_oldAttributes = $record->_attributes;
1199-
$record->_related = [];
1200-
$record->_relationsDependencies = [];
12011177
}
12021178

12031179
/**
@@ -1731,41 +1707,6 @@ public function offsetUnset($offset)
17311707
}
17321708
}
17331709

1734-
/**
1735-
* Resets dependent related models checking if their links contain specific attribute.
1736-
* @param string $attribute The changed attribute name.
1737-
*/
1738-
private function resetDependentRelations($attribute)
1739-
{
1740-
foreach ($this->_relationsDependencies[$attribute] as $relation) {
1741-
unset($this->_related[$relation]);
1742-
}
1743-
unset($this->_relationsDependencies[$attribute]);
1744-
}
1745-
1746-
/**
1747-
* Sets relation dependencies for a property
1748-
* @param string $name property name
1749-
* @param ActiveQueryInterface $relation relation instance
1750-
* @param string|null $viaRelationName intermediate relation
1751-
*/
1752-
private function setRelationDependencies($name, $relation, $viaRelationName = null)
1753-
{
1754-
if (empty($relation->via) && $relation->link) {
1755-
foreach ($relation->link as $attribute) {
1756-
$this->_relationsDependencies[$attribute][$name] = $name;
1757-
if ($viaRelationName !== null) {
1758-
$this->_relationsDependencies[$attribute][] = $viaRelationName;
1759-
}
1760-
}
1761-
} elseif ($relation->via instanceof ActiveQueryInterface) {
1762-
$this->setRelationDependencies($name, $relation->via);
1763-
} elseif (is_array($relation->via)) {
1764-
list($viaRelationName, $viaQuery) = $relation->via;
1765-
$this->setRelationDependencies($name, $viaQuery, $viaRelationName);
1766-
}
1767-
}
1768-
17691710
/**
17701711
* @param mixed $newValue
17711712
* @param mixed $oldValue

tests/data/ar/Customer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
* @property string $email
1919
* @property string $address
2020
* @property int $status
21+
* @property int $profile_id
2122
*
2223
* @method CustomerQuery findBySql($sql, $params = []) static
2324
*/

tests/data/cubrid.sql

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ DROP TABLE IF EXISTS "constraints";
1919
DROP TABLE IF EXISTS "animal";
2020
DROP TABLE IF EXISTS "default_pk";
2121
DROP TABLE IF EXISTS "document";
22-
DROP TABLE IF EXISTS "dossier";
23-
DROP TABLE IF EXISTS "employee";
24-
DROP TABLE IF EXISTS "department";
2522
DROP VIEW IF EXISTS "animal_view";
2623
DROP TABLE IF EXISTS "T_constraints_4";
2724
DROP TABLE IF EXISTS "T_constraints_3";
@@ -167,28 +164,6 @@ CREATE TABLE "document" (
167164
PRIMARY KEY ("id")
168165
);
169166

170-
CREATE TABLE "department" (
171-
"id" int(11) NOT NULL AUTO_INCREMENT,
172-
"title" VARCHAR(255) NOT NULL,
173-
PRIMARY KEY ("id")
174-
);
175-
176-
CREATE TABLE "employee" (
177-
"id" int(11) NOT NULL,
178-
"department_id" int(11) NOT NULL,
179-
"first_name" VARCHAR(255) NOT NULL,
180-
"last_name" VARCHAR(255) NOT NULL,
181-
PRIMARY KEY ("id", "department_id")
182-
);
183-
184-
CREATE TABLE "dossier" (
185-
"id" int(11) NOT NULL AUTO_INCREMENT,
186-
"department_id" int(11) NOT NULL,
187-
"employee_id" int(11) NOT NULL,
188-
"summary" VARCHAR(255) NOT NULL,
189-
PRIMARY KEY ("id")
190-
);
191-
192167
CREATE VIEW "animal_view" AS SELECT * FROM "animal";
193168

194169
INSERT INTO "animal" ("type") VALUES ('yiiunit\data\ar\Cat');
@@ -234,17 +209,6 @@ INSERT INTO "order_item_with_null_fk" (order_id, item_id, quantity, subtotal) VA
234209

235210
INSERT INTO "document" (title, content, version) VALUES ('Yii 2.0 guide', 'This is Yii 2.0 guide', 0);
236211

237-
INSERT INTO "department" (id, title) VALUES (1, 'IT');
238-
INSERT INTO "department" (id, title) VALUES (2, 'accounting');
239-
240-
INSERT INTO "employee" (id, department_id, first_name, last_name) VALUES (1, 1, 'John', 'Doe');
241-
INSERT INTO "employee" (id, department_id, first_name, last_name) VALUES (1, 2, 'Ann', 'Smith');
242-
INSERT INTO "employee" (id, department_id, first_name, last_name) VALUES (2, 2, 'Will', 'Smith');
243-
244-
INSERT INTO "dossier" (id, department_id, employee_id, summary) VALUES (1, 1, 1, 'Excellent employee.');
245-
INSERT INTO "dossier" (id, department_id, employee_id, summary) VALUES (2, 2, 1, 'Brilliant employee.');
246-
INSERT INTO "dossier" (id, department_id, employee_id, summary) VALUES (3, 2, 2, 'Good employee.');
247-
248212
/* bit test, see https://github.com/yiisoft/yii2/issues/9006 */
249213

250214
DROP TABLE IF EXISTS `bit_values`;

tests/data/mssql.sql

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,6 @@ IF OBJECT_ID('[dbo].[negative_default_values]', 'U') IS NOT NULL DROP TABLE [dbo
1515
IF OBJECT_ID('[dbo].[animal]', 'U') IS NOT NULL DROP TABLE [dbo].[animal];
1616
IF OBJECT_ID('[dbo].[default_pk]', 'U') IS NOT NULL DROP TABLE [dbo].[default_pk];
1717
IF OBJECT_ID('[dbo].[document]', 'U') IS NOT NULL DROP TABLE [dbo].[document];
18-
IF OBJECT_ID('[dbo].[dossier]', 'U') IS NOT NULL DROP TABLE [dbo].[dossier];
19-
IF OBJECT_ID('[dbo].[employee]', 'U') IS NOT NULL DROP TABLE [dbo].[employee];
20-
IF OBJECT_ID('[dbo].[department]', 'U') IS NOT NULL DROP TABLE [dbo].[department];
2118
IF OBJECT_ID('[dbo].[animal_view]', 'V') IS NOT NULL DROP VIEW [dbo].[animal_view];
2219
IF OBJECT_ID('[T_constraints_4]', 'U') IS NOT NULL DROP TABLE [T_constraints_4];
2320
IF OBJECT_ID('[T_constraints_3]', 'U') IS NOT NULL DROP TABLE [T_constraints_3];
@@ -179,35 +176,6 @@ CREATE TABLE [dbo].[document] (
179176
) ON [PRIMARY]
180177
);
181178

182-
CREATE TABLE [dbo].[department] (
183-
[id] [int] IDENTITY NOT NULL,
184-
[title] [varchar](255) NOT NULL,
185-
CONSTRAINT [PK_department_pk] PRIMARY KEY CLUSTERED (
186-
[id] ASC
187-
) ON [PRIMARY]
188-
);
189-
190-
CREATE TABLE [dbo].[employee] (
191-
[id] [int] NOT NULL,
192-
[department_id] [int] NOT NULL,
193-
[first_name] [varchar](255) NOT NULL,
194-
[last_name] [varchar](255) NOT NULL,
195-
CONSTRAINT [PK_employee_pk] PRIMARY KEY CLUSTERED (
196-
[id] ASC,
197-
[department_id] ASC
198-
) ON [PRIMARY]
199-
);
200-
201-
CREATE TABLE [dbo].[dossier] (
202-
[id] [int] IDENTITY NOT NULL,
203-
[department_id] [int] NOT NULL,
204-
[employee_id] [int] NOT NULL,
205-
[summary] [varchar](255) NOT NULL,
206-
CONSTRAINT [PK_dossier_pk] PRIMARY KEY CLUSTERED (
207-
[id] ASC
208-
) ON [PRIMARY]
209-
);
210-
211179
CREATE VIEW [dbo].[animal_view] AS SELECT * FROM [dbo].[animal];
212180

213181
INSERT INTO [dbo].[animal] (type) VALUES ('yiiunit\data\ar\Cat');
@@ -253,21 +221,6 @@ INSERT INTO [dbo].[order_item_with_null_fk] ([order_id], [item_id], [quantity],
253221

254222
INSERT INTO [dbo].[document] ([title], [content], [version]) VALUES ('Yii 2.0 guide', 'This is Yii 2.0 guide', 0);
255223

256-
SET IDENTITY_INSERT [dbo].[department] ON;
257-
INSERT INTO [dbo].[department] (id, title) VALUES (1, 'IT');
258-
INSERT INTO [dbo].[department] (id, title) VALUES (2, 'accounting');
259-
SET IDENTITY_INSERT [dbo].[department] OFF;
260-
261-
INSERT INTO [dbo].[employee] (id, department_id, first_name, last_name) VALUES (1, 1, 'John', 'Doe');
262-
INSERT INTO [dbo].[employee] (id, department_id, first_name, last_name) VALUES (1, 2, 'Ann', 'Smith');
263-
INSERT INTO [dbo].[employee] (id, department_id, first_name, last_name) VALUES (2, 2, 'Will', 'Smith');
264-
265-
SET IDENTITY_INSERT [dbo].[dossier] ON;
266-
INSERT INTO [dbo].[dossier] (id, department_id, employee_id, summary) VALUES (1, 1, 1, 'Excellent employee.');
267-
INSERT INTO [dbo].[dossier] (id, department_id, employee_id, summary) VALUES (2, 2, 1, 'Brilliant employee.');
268-
INSERT INTO [dbo].[dossier] (id, department_id, employee_id, summary) VALUES (3, 2, 2, 'Good employee.');
269-
SET IDENTITY_INSERT [dbo].[dossier] OFF;
270-
271224
/* bit test, see https://github.com/yiisoft/yii2/issues/9006 */
272225

273226
IF OBJECT_ID('[dbo].[bit_values]', 'U') IS NOT NULL DROP TABLE [dbo].[bit_values];

tests/data/mysql.sql

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ DROP TABLE IF EXISTS `animal` CASCADE;
2020
DROP TABLE IF EXISTS `default_pk` CASCADE;
2121
DROP TABLE IF EXISTS `document` CASCADE;
2222
DROP TABLE IF EXISTS `comment` CASCADE;
23-
DROP TABLE IF EXISTS `dossier`;
24-
DROP TABLE IF EXISTS `employee`;
25-
DROP TABLE IF EXISTS `department`;
2623
DROP TABLE IF EXISTS `storage`;
2724
DROP TABLE IF EXISTS `alpha`;
2825
DROP TABLE IF EXISTS `beta`;
@@ -186,28 +183,6 @@ CREATE TABLE `comment` (
186183
PRIMARY KEY (id)
187184
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
188185

189-
CREATE TABLE `department` (
190-
`id` INT(11) NOT NULL AUTO_INCREMENT,
191-
title VARCHAR(255) NOT NULL,
192-
PRIMARY KEY (`id`)
193-
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
194-
195-
CREATE TABLE `employee` (
196-
`id` INT(11) NOT NULL,
197-
`department_id` INT(11) NOT NULL,
198-
`first_name` VARCHAR(255) NOT NULL,
199-
`last_name` VARCHAR(255) NOT NULL,
200-
PRIMARY KEY (`id`, `department_id`)
201-
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
202-
203-
CREATE TABLE `dossier` (
204-
`id` INT(11) NOT NULL AUTO_INCREMENT,
205-
`department_id` INT(11) NOT NULL,
206-
`employee_id` INT(11) NOT NULL,
207-
`summary` VARCHAR(255) NOT NULL,
208-
PRIMARY KEY (`id`)
209-
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
210-
211186
CREATE TABLE `storage` (
212187
`id` INT(11) NOT NULL AUTO_INCREMENT,
213188
`data` JSON NOT NULL,
@@ -271,17 +246,6 @@ INSERT INTO `order_item_with_null_fk` (order_id, item_id, quantity, subtotal) VA
271246

272247
INSERT INTO `document` (title, content, version) VALUES ('Yii 2.0 guide', 'This is Yii 2.0 guide', 0);
273248

274-
INSERT INTO `department` (id, title) VALUES (1, 'IT');
275-
INSERT INTO `department` (id, title) VALUES (2, 'accounting');
276-
277-
INSERT INTO `employee` (id, department_id, first_name, last_name) VALUES (1, 1, 'John', 'Doe');
278-
INSERT INTO `employee` (id, department_id, first_name, last_name) VALUES (1, 2, 'Ann', 'Smith');
279-
INSERT INTO `employee` (id, department_id, first_name, last_name) VALUES (2, 2, 'Will', 'Smith');
280-
281-
INSERT INTO `dossier` (id, department_id, employee_id, summary) VALUES (1, 1, 1, 'Excellent employee.');
282-
INSERT INTO `dossier` (id, department_id, employee_id, summary) VALUES (2, 2, 1, 'Brilliant employee.');
283-
INSERT INTO `dossier` (id, department_id, employee_id, summary) VALUES (3, 2, 2, 'Good employee.');
284-
285249
INSERT INTO `alpha` (id, string_identifier) VALUES (1, '1');
286250
INSERT INTO `alpha` (id, string_identifier) VALUES (2, '1a');
287251
INSERT INTO `alpha` (id, string_identifier) VALUES (3, '01');

0 commit comments

Comments
 (0)