Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 8125e06

Browse files
committedSep 21, 2023
Fix #[16222], Added Model::setRelated()
1 parent 450009c commit 8125e06

File tree

3 files changed

+253
-88
lines changed

3 files changed

+253
-88
lines changed
 

‎CHANGELOG-5.0.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Changelog
22

3-
## [5.4.0](https://github.com/phalcon/cphalcon/releases/tag/v5.3.1) (xxxx-xx-xx)
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)
47

58
### Fixed
69
- Model Annotation strategy did not work with empty_string [#16426] (https://github.com/phalcon/cphalcon/issues/16426)

‎phalcon/Mvc/Model.zep

+104-87
Original file line numberDiff line numberDiff line change
@@ -412,97 +412,15 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
412412
*/
413413
public function __set(string property, value)
414414
{
415-
var lowerProperty, modelName, manager, relation, referencedModel, item,
416-
dirtyState;
417-
array related;
415+
var manager, related;
418416

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

@@ -2115,6 +2033,105 @@ abstract class Model extends AbstractInjectionAware implements EntityInterface,
21152033
return result;
21162034
}
21172035

2036+
/**
2037+
* Sets related objects based on Alias and type of value (Model or array),
2038+
* by setting relations, the dirtyState are set acordingly to Transient has opt-in
2039+
*
2040+
* @param string alias
2041+
* @param mixed value
2042+
* @return \Phalcon\Mvc\Model|array|null Null is returned if no relation was found
2043+
*/
2044+
public function setRelated(string alias, value) -> mixed
2045+
{
2046+
var relation, className, manager, lowerAlias, referencedModel, item, related;
2047+
2048+
let manager = this->getModelsManager();
2049+
let className = get_class(this);
2050+
let lowerAlias = strtolower(alias);
2051+
/**
2052+
* Query the relation by alias
2053+
*/
2054+
let relation = <RelationInterface> manager->getRelationByAlias(
2055+
className,
2056+
lowerAlias
2057+
);
2058+
2059+
if likely typeof relation === "object" {
2060+
let className = get_class(this),
2061+
manager = <ManagerInterface> this->modelsManager,
2062+
lowerAlias = strtolower(alias);
2063+
2064+
if typeof value === "object" && value instanceof ModelInterface {
2065+
/**
2066+
* Opt-in dirty state
2067+
*/
2068+
value->setDirtyState(self::DIRTY_STATE_TRANSIENT);
2069+
let this->dirtyState = self::DIRTY_STATE_TRANSIENT;
2070+
/**
2071+
* Add to dirtyRelated and remove from related.
2072+
*/
2073+
let this->dirtyRelated[lowerAlias] = value;
2074+
unset(this->related[lowerAlias]);
2075+
return value;
2076+
}
2077+
2078+
/**
2079+
* Check if the value is an array
2080+
*/
2081+
elseif typeof value === "array" {
2082+
switch relation->getType() {
2083+
case Relation::BELONGS_TO:
2084+
case Relation::HAS_ONE:
2085+
/**
2086+
* Load referenced model from local cache if its possible
2087+
*/
2088+
let referencedModel = manager->load(
2089+
relation->getReferencedModel()
2090+
);
2091+
2092+
if typeof referencedModel === "object" {
2093+
referencedModel->assign(value);
2094+
let this->dirtyRelated[lowerAlias] = referencedModel;
2095+
/**
2096+
* Add to dirtyRelated and remove from related.
2097+
*/
2098+
unset(this->related[lowerAlias]);
2099+
return referencedModel;
2100+
}
2101+
break;
2102+
2103+
case Relation::HAS_MANY:
2104+
case Relation::HAS_MANY_THROUGH:
2105+
let related = [];
2106+
/**
2107+
* this is probably not needed
2108+
*/
2109+
for item in value {
2110+
if typeof item === "object" {
2111+
if item instanceof ModelInterface {
2112+
let related[] = item;
2113+
}
2114+
}
2115+
}
2116+
/**
2117+
* Add to dirtyRelated and remove from related.
2118+
*/
2119+
unset this->related[lowerAlias];
2120+
2121+
if count(related) > 0 {
2122+
let this->dirtyRelated[lowerAlias] = related,
2123+
this->dirtyState = self::DIRTY_STATE_TRANSIENT;
2124+
} else {
2125+
unset this->dirtyRelated[lowerAlias];
2126+
}
2127+
2128+
return value;
2129+
}
2130+
}
2131+
}
2132+
return null;
2133+
}
2134+
21182135
/**
21192136
* Checks if saved related records have already been loaded.
21202137
*
+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 <team@phalcon.io>
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)
Please sign in to comment.