Skip to content

Commit 578042e

Browse files
committed
Fix #[16222], Added Model::setRelated()
1 parent df62245 commit 578042e

File tree

3 files changed

+254
-87
lines changed

3 files changed

+254
-87
lines changed

CHANGELOG-5.0.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## [5.4.0](https://github.com/phalcon/cphalcon/releases/tag/v5.4.0) (xxxx-xx-xx)
4+
5+
### Added
6+
- Added `Phalcon\Mvc\Model::setRelated()` to allow setting related models and automaticly de added to the dirtyRelated list [#16222] (https://github.com/phalcon/cphalcon/issues/16222)
7+
38
## [5.3.0](https://github.com/phalcon/cphalcon/releases/tag/v5.3.0) (2023-08-15)
49

510
### Added

phalcon/Mvc/Model.zep

+104-87
Original file line numberDiff line numberDiff line change
@@ -410,97 +410,15 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
410410
*/
411411
public function __set(string property, value)
412412
{
413-
var lowerProperty, modelName, manager, relation, referencedModel, item,
414-
dirtyState;
415-
array related;
413+
var manager, related;
416414

417415
/**
418416
* Values are probably relationships if they are objects
419417
*/
420-
if typeof value === "object" && value instanceof ModelInterface {
421-
let lowerProperty = strtolower(property),
422-
modelName = get_class(this),
423-
manager = this->getModelsManager(),
424-
relation = <RelationInterface> manager->getRelationByAlias(
425-
modelName,
426-
lowerProperty
427-
);
428-
429-
if typeof relation === "object" {
430-
let dirtyState = this->dirtyState;
431-
432-
if (value->getDirtyState() != dirtyState) {
433-
let dirtyState = self::DIRTY_STATE_TRANSIENT;
434-
}
435-
436-
unset this->related[lowerProperty];
437-
438-
let this->dirtyRelated[lowerProperty] = value,
439-
this->dirtyState = dirtyState;
440-
441-
return value;
442-
}
443-
}
444-
445-
/**
446-
* Check if the value is an array
447-
*/
448-
elseif typeof value === "array" {
449-
let lowerProperty = strtolower(property),
450-
modelName = get_class(this),
451-
manager = this->getModelsManager(),
452-
relation = <RelationInterface> manager->getRelationByAlias(
453-
modelName,
454-
lowerProperty
455-
);
456-
457-
if typeof relation === "object" {
458-
switch relation->getType() {
459-
case Relation::BELONGS_TO:
460-
case Relation::HAS_ONE:
461-
/**
462-
* Load referenced model from local cache if its possible
463-
*/
464-
let referencedModel = manager->load(
465-
relation->getReferencedModel()
466-
);
467-
468-
if typeof referencedModel === "object" {
469-
referencedModel->assign(value);
470-
471-
unset this->related[lowerProperty];
472-
473-
let this->dirtyRelated[lowerProperty] = referencedModel,
474-
this->dirtyState = self::DIRTY_STATE_TRANSIENT;
475-
476-
return value;
477-
}
478-
479-
break;
480-
481-
case Relation::HAS_MANY:
482-
case Relation::HAS_MANY_THROUGH:
483-
let related = [];
484-
485-
for item in value {
486-
if typeof item === "object" {
487-
if item instanceof ModelInterface {
488-
let related[] = item;
489-
}
490-
}
491-
}
492-
493-
unset this->related[lowerProperty];
494-
495-
if count(related) > 0 {
496-
let this->dirtyRelated[lowerProperty] = related,
497-
this->dirtyState = self::DIRTY_STATE_TRANSIENT;
498-
} else {
499-
unset this->dirtyRelated[lowerProperty];
500-
}
501-
502-
return value;
503-
}
418+
if (typeof value === "object" && value instanceof ModelInterface ) || typeof value === "array" {
419+
let related = this->setRelated(property, value);
420+
if null !== related {
421+
return related;
504422
}
505423
}
506424

@@ -2113,6 +2031,105 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
21132031
return result;
21142032
}
21152033

2034+
/**
2035+
* Sets related objects based on Alias and type of value (Model or array),
2036+
* by setting relations, the dirtyState are set acordingly to Transient has opt-in
2037+
*
2038+
* @param string alias
2039+
* @param mixed value
2040+
* @return \Phalcon\Mvc\Model|array|null Null is returned if no relation was found
2041+
*/
2042+
public function setRelated(string alias, value) -> mixed
2043+
{
2044+
var relation, className, manager, lowerAlias, referencedModel, item, related;
2045+
2046+
let manager = this->getModelsManager();
2047+
let className = get_class(this);
2048+
let lowerAlias = strtolower(alias);
2049+
/**
2050+
* Query the relation by alias
2051+
*/
2052+
let relation = <RelationInterface> manager->getRelationByAlias(
2053+
className,
2054+
lowerAlias
2055+
);
2056+
2057+
if likely typeof relation === "object" {
2058+
let className = get_class(this),
2059+
manager = <ManagerInterface> this->modelsManager,
2060+
lowerAlias = strtolower(alias);
2061+
2062+
if typeof value === "object" && value instanceof ModelInterface {
2063+
/**
2064+
* Opt-in dirty state
2065+
*/
2066+
value->setDirtyState(self::DIRTY_STATE_TRANSIENT);
2067+
let this->dirtyState = self::DIRTY_STATE_TRANSIENT;
2068+
/**
2069+
* Add to dirtyRelated and remove from related.
2070+
*/
2071+
let this->dirtyRelated[lowerAlias] = value;
2072+
unset(this->related[lowerAlias]);
2073+
return value;
2074+
}
2075+
2076+
/**
2077+
* Check if the value is an array
2078+
*/
2079+
elseif typeof value === "array" {
2080+
switch relation->getType() {
2081+
case Relation::BELONGS_TO:
2082+
case Relation::HAS_ONE:
2083+
/**
2084+
* Load referenced model from local cache if its possible
2085+
*/
2086+
let referencedModel = manager->load(
2087+
relation->getReferencedModel()
2088+
);
2089+
2090+
if typeof referencedModel === "object" {
2091+
referencedModel->assign(value);
2092+
let this->dirtyRelated[lowerAlias] = referencedModel;
2093+
/**
2094+
* Add to dirtyRelated and remove from related.
2095+
*/
2096+
unset(this->related[lowerAlias]);
2097+
return referencedModel;
2098+
}
2099+
break;
2100+
2101+
case Relation::HAS_MANY:
2102+
case Relation::HAS_MANY_THROUGH:
2103+
let related = [];
2104+
/**
2105+
* this is probably not needed
2106+
*/
2107+
for item in value {
2108+
if typeof item === "object" {
2109+
if item instanceof ModelInterface {
2110+
let related[] = item;
2111+
}
2112+
}
2113+
}
2114+
/**
2115+
* Add to dirtyRelated and remove from related.
2116+
*/
2117+
unset this->related[lowerAlias];
2118+
2119+
if count(related) > 0 {
2120+
let this->dirtyRelated[lowerAlias] = related,
2121+
this->dirtyState = self::DIRTY_STATE_TRANSIENT;
2122+
} else {
2123+
unset this->dirtyRelated[lowerAlias];
2124+
}
2125+
2126+
return value;
2127+
}
2128+
}
2129+
}
2130+
return null;
2131+
}
2132+
21162133
/**
21172134
* Checks if saved related records have already been loaded.
21182135
*
+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Phalcon Framework.
5+
*
6+
* (c) Phalcon Team <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE.txt
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Phalcon\Tests\Database\Mvc\Model;
15+
16+
use DatabaseTester;
17+
use PDO;
18+
use Phalcon\Tests\Fixtures\Migrations\CustomersMigration;
19+
use Phalcon\Tests\Fixtures\Migrations\InvoicesMigration;
20+
use Phalcon\Tests\Fixtures\Traits\DiTrait;
21+
use Phalcon\Tests\Models\Customers;
22+
use Phalcon\Tests\Models\Invoices;
23+
24+
use function uniqid;
25+
26+
/**
27+
* Class GetRelatedCest
28+
*/
29+
class SetRelatedCest
30+
{
31+
use DiTrait;
32+
33+
/**
34+
* @param DatabaseTester $I
35+
*/
36+
public function _before(DatabaseTester $I)
37+
{
38+
$this->setNewFactoryDefault();
39+
$this->setDatabase($I);
40+
}
41+
42+
/**
43+
* Tests Phalcon\Mvc\Model :: getRelated()
44+
*
45+
* @param DatabaseTester $I
46+
*
47+
* @since 2023-08-15
48+
*
49+
* @group mysql
50+
* @group pgsql
51+
* @group sqlite
52+
*/
53+
public function mvcModelSetRelated(DatabaseTester $I)
54+
{
55+
$I->wantToTest('Mvc\Model - setRelated()');
56+
57+
/** @var PDO $connection */
58+
$connection = $I->getConnection();
59+
60+
$custId = 2;
61+
62+
$firstName = uniqid('cust-', true);
63+
$lastName = uniqid('cust-', true);
64+
65+
$customersMigration = new CustomersMigration($connection);
66+
$customersMigration->insert($custId, 0, $firstName, $lastName);
67+
68+
$paidInvoiceId = 4;
69+
$unpaidInvoiceId = 5;
70+
71+
$title = uniqid('inv-');
72+
73+
$invoicesMigration = new InvoicesMigration($connection);
74+
$invoicesMigration->insert(
75+
$paidInvoiceId,
76+
$custId,
77+
Invoices::STATUS_PAID,
78+
$title . '-paid'
79+
);
80+
$invoicesMigration->insert(
81+
$unpaidInvoiceId,
82+
$custId,
83+
Invoices::STATUS_UNPAID,
84+
$title . '-unpaid'
85+
);
86+
87+
/**
88+
* @var Customers $customer
89+
*/
90+
$customer = Customers::findFirst($custId);
91+
92+
$invoices = [];
93+
$expectedTitle = [];
94+
foreach ($customer->Invoices as $invoice) {
95+
$invoices[] = $invoice;
96+
$expectedTitle[] = $invoice->inv_title . 'updated';
97+
$invoice->inv_title = $invoice->inv_title . 'updated';
98+
}
99+
100+
$customer->setRelated('Invoices', $invoices);
101+
102+
$invoices = $customer->Invoices;
103+
104+
$I->assertIsArray($invoices);
105+
106+
$expected = 2;
107+
$actual = count($invoices);
108+
$I->assertEquals($expected, $actual);
109+
110+
$actual = $customer->save();
111+
112+
$I->assertTrue($actual);
113+
114+
$invoice = $invoices[0];
115+
$actual = $invoice->getDirtyState();
116+
117+
$I->assertEquals(0, $actual);
118+
119+
$expected = $expectedTitle[0];
120+
$actual = $invoice->inv_title;
121+
$I->assertSame($expected, $actual);
122+
123+
$invoice = $invoices[1];
124+
$actual = $invoice->getDirtyState();
125+
$expected = 0;
126+
$I->assertEquals($expected, $actual);
127+
128+
$expected = $expectedTitle[1];
129+
$actual = $invoice->inv_title;
130+
$I->assertSame($expected, $actual);
131+
132+
$actual = $customer->getDirtyState();
133+
$expected = 0;
134+
$I->assertEquals($expected, $actual);
135+
136+
$invoice->Customer = $customer;
137+
$actual = $invoice->getDirtyState();
138+
$expected = 1;
139+
$I->assertEquals($expected, $actual);
140+
141+
$actual = $customer->getDirtyState();
142+
$expected = 1;
143+
$I->assertEquals($expected, $actual);
144+
}
145+
}

0 commit comments

Comments
 (0)