From f4ef3b4833fc243a714745ea0cd27389dfbf5d98 Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Thu, 8 Feb 2018 11:44:27 +0530 Subject: [PATCH 01/22] Added onCondition and where condition for the FK delete queries #54 Fixes --- RelationTrait.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/RelationTrait.php b/RelationTrait.php index e3c9aa6..cf4ecc8 100644 --- a/RelationTrait.php +++ b/RelationTrait.php @@ -178,6 +178,14 @@ public function saveAll($skippedRelations = []) $notDeletedFK = []; $relPKAttr = ($AQ->multiple) ? $records[0]->primaryKey() : $records->primaryKey(); $isManyMany = (count($relPKAttr) > 1); + $conditions = []; + if (!empty($AQ->on)) { + $conditions[] = $AQ->on; + } + + if (!empty($AQ->where)) { + $conditions[] = $AQ->where; + } if ($AQ->multiple) { /* @var $relModel ActiveRecord */ foreach ($records as $index => $relModel) { @@ -209,6 +217,11 @@ public function saveAll($skippedRelations = []) $notIn = ['not in', $attr, $value]; array_push($query, $notIn); } + if (!empty($conditions)) { + foreach ($conditions as $condition) { + array_push($query, $condition); + } + } try { if ($isSoftDelete) { $relModel->updateAll($this->_rt_softdelete, $query); @@ -222,6 +235,11 @@ public function saveAll($skippedRelations = []) } else { // Has Many $query = ['and', $notDeletedFK, ['not in', $relPKAttr[0], $notDeletedPK]]; + if (!empty($conditions)) { + foreach ($conditions as $condition) { + array_push($query, $condition); + } + } if (!empty($notDeletedPK)) { try { if ($isSoftDelete) { From 486fcf3d1922a0fdae46a4d8dd42f23f51a270e0 Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Fri, 9 Feb 2018 15:10:57 +0530 Subject: [PATCH 02/22] Publishing to composer --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3dcf7b1..7b06796 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "mootensai/yii2-relation-trait", + "name": "deadmantfa/yii2-relation-trait", "type": "yii2-extension", "description": "Yii 2 Models load with relation, & transaction save with relation", "keywords": ["Yii2","relation","load","save","transaction","loadwithrelation", "savewithrelation", "related", "saveall", "loadall"], From a3a450a3544b2ff3d967c5c329b2d6c1ce34916d Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Fri, 9 Feb 2018 15:13:29 +0530 Subject: [PATCH 03/22] Publishing to composer --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 6110179..5fd24be 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ # yii2-relation-trait + +##I am not the creator of this extension, I have made a bug fix and made a pull request. I am just publishing this for my own usage. Feel free to use it. + +##All thanks to @mootensai for creating the extension. + Yii 2 Models add functionality for load with relation (loadAll($POST)), & transactional save with relation (saveAll()) PLUS soft delete/restore feature! From ebedf9102d948b6051067bdda6e577c747010207 Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Fri, 9 Feb 2018 15:21:41 +0530 Subject: [PATCH 04/22] Publishing to composer --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5fd24be..2599289 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ ##All thanks to @mootensai for creating the extension. +##This is not to replace the package maintained by @mootensai + Yii 2 Models add functionality for load with relation (loadAll($POST)), & transactional save with relation (saveAll()) PLUS soft delete/restore feature! From 9f221762adddf147a6954394819797edcdae83d3 Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Fri, 9 Feb 2018 16:13:36 +0530 Subject: [PATCH 05/22] Publishing to composer --- RelationTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RelationTrait.php b/RelationTrait.php index cf4ecc8..cf16530 100644 --- a/RelationTrait.php +++ b/RelationTrait.php @@ -7,7 +7,7 @@ * @since 1.0 */ -namespace mootensai\relation; +namespace deadmantfa\relation; use Yii; use yii\db\ActiveQuery; From cd084db79d1c5970629ddd6dd0729fe6757c39e8 Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Fri, 9 Feb 2018 16:19:07 +0530 Subject: [PATCH 06/22] Publishing to composer --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2599289..708a819 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # yii2-relation-trait -##I am not the creator of this extension, I have made a bug fix and made a pull request. I am just publishing this for my own usage. Feel free to use it. +#### I am not the creator of this extension, I have made a bug fix and made a pull request. I am just publishing this for my own usage. Feel free to use it. -##All thanks to @mootensai for creating the extension. +#### All thanks to @mootensai for creating the extension. -##This is not to replace the package maintained by @mootensai +#### This is not to replace the package maintained by @mootensai Yii 2 Models add functionality for load with relation (loadAll($POST)), & transactional save with relation (saveAll()) From f988921b4a5f7395d24f4f7c186b6dbf4f57e32c Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Fri, 9 Feb 2018 17:19:28 +0530 Subject: [PATCH 07/22] Publishing to composer --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7b06796..8270d70 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ }, "autoload": { "psr-4": { - "mootensai\\relation\\": "" + "deadmantfa\\relation\\": "" } } } From 6bd3a6ab656a3b060ae465391dfd3f198c410bb9 Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Fri, 9 Feb 2018 17:31:44 +0530 Subject: [PATCH 08/22] Publishing to composer --- composer.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 8270d70..56e378b 100644 --- a/composer.json +++ b/composer.json @@ -6,13 +6,17 @@ "homepage": "http://github.com/mootensai/yii2-relation-trait", "license": "BSD-3-Clause", "support": { - "issues": "https://github.com/mootensai/yii2-relation-trait/issues", - "source": "https://github.com/mootensai/yii2-relation-trait" + "issues": "https://github.com/deadmantfa/yii2-relation-trait/issues", + "source": "https://github.com/deadmantfa/yii2-relation-trait" }, "authors": [ { "name": "Yohanes Candrajaya", "email": "moo.tensai@gmail.com" + }, + { + "name": "Wenceslaus Dsilva", + "email": "wenceslausdsilva@gmail.com" } ], "require": { From 7d39ff454845c445b7f8c1a2df18aef6a7f9a091 Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Fri, 9 Feb 2018 17:37:19 +0530 Subject: [PATCH 09/22] Publishing to composer --- README.md | 6 +++--- composer.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 708a819..a5ebdeb 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,13 @@ The preferred way to install this extension is through [composer](http://getcomp Either run ```bash -$ composer require 'mootensai/yii2-relation-trait:dev-master' +$ composer require 'deadmantfa/yii2-relation-trait:dev-master' ``` or add ``` -"mootensai/yii2-relation-trait": "*" +"deadmantfa/yii2-relation-trait": "*" ``` to the `require` section of your `composer.json` file. @@ -52,7 +52,7 @@ to the `require` section of your `composer.json` file. ```php class MyModel extends ActiveRecord{ - use \mootensai\relation\RelationTrait; + use \deadmantfa\relation\RelationTrait; } ``` diff --git a/composer.json b/composer.json index 56e378b..72f251a 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "type": "yii2-extension", "description": "Yii 2 Models load with relation, & transaction save with relation", "keywords": ["Yii2","relation","load","save","transaction","loadwithrelation", "savewithrelation", "related", "saveall", "loadall"], - "homepage": "http://github.com/mootensai/yii2-relation-trait", + "homepage": "http://github.com/deadmantfa/yii2-relation-trait", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/deadmantfa/yii2-relation-trait/issues", From 3899b65e52908cf31cc60b9527783616ffb14bd7 Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Mon, 12 Feb 2018 02:05:02 +0530 Subject: [PATCH 10/22] Minor fix --- RelationTrait.php | 87 +++++++++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 29 deletions(-) diff --git a/RelationTrait.php b/RelationTrait.php index cf16530..41c0467 100644 --- a/RelationTrait.php +++ b/RelationTrait.php @@ -289,37 +289,28 @@ public function saveAll($skippedRelations = []) } } - //No Children left - $relAvail = array_keys($this->relatedRecords); - $relData = $this->getRelationData(); - $allRel = array_keys($relData); - $noChildren = array_diff($allRel, $relAvail); - - foreach ($noChildren as $relName) { - /* @var $relModel ActiveRecord */ - if (empty($relData[$relName]['via']) && !in_array($relName, $skippedRelations)) { - $relModel = new $relData[$relName]['modelClass']; - $condition = []; - $isManyMany = count($relModel->primaryKey()) > 1; - if ($isManyMany) { - foreach ($relData[$relName]['link'] as $k => $v) { - $condition[$k] = $this->$v; - } - try { - if ($isSoftDelete) { - $relModel->updateAll($this->_rt_softdelete, ['and', $condition]); - } else { - $relModel->deleteAll(['and', $condition]); - } - } catch (IntegrityException $exc) { - $this->addError($relData[$relName]['name'], Yii::t('mtrelt', "Data can't be deleted because it's still used by another data.")); - $error = true; - } - } else { - if ($relData[$relName]['ismultiple']) { + if (!$isNewRecord) { + //No Children left + $relAvail = array_keys($this->relatedRecords); + $relData = $this->getRelationData(); + $allRel = array_keys($relData); + $noChildren = array_diff($allRel, $relAvail); + + foreach ($noChildren as $relName) { + /* @var $relModel ActiveRecord */ + if (empty($relData[$relName]['via']) && !in_array($relName, $skippedRelations)) { + $relModel = new $relData[$relName]['modelClass']; + $condition = []; + $isManyMany = count($relModel->primaryKey()) > 1; + if ($isManyMany) { foreach ($relData[$relName]['link'] as $k => $v) { $condition[$k] = $this->$v; } + if (!empty($relData[$relName]['where'])) { + foreach ($relData[$relName]['where'] as $k => $v) { + $condition[$k] = $v; + } + } try { if ($isSoftDelete) { $relModel->updateAll($this->_rt_softdelete, ['and', $condition]); @@ -330,12 +321,32 @@ public function saveAll($skippedRelations = []) $this->addError($relData[$relName]['name'], Yii::t('mtrelt', "Data can't be deleted because it's still used by another data.")); $error = true; } + } else { + if ($relData[$relName]['ismultiple']) { + foreach ($relData[$relName]['link'] as $k => $v) { + $condition[$k] = $this->$v; + } + if (!empty($relData[$relName]['where'])) { + foreach ($relData[$relName]['where'] as $k => $v) { + $condition[$k] = $v; + } + } + try { + if ($isSoftDelete) { + $relModel->updateAll($this->_rt_softdelete, ['and', $condition]); + } else { + $relModel->deleteAll(['and', $condition]); + } + } catch (IntegrityException $exc) { + $this->addError($relData[$relName]['name'], Yii::t('mtrelt', "Data can't be deleted because it's still used by another data.")); + $error = true; + } + } } } } } - if ($error) { $trans->rollback(); $this->isNewRecord = $isNewRecord; @@ -475,6 +486,15 @@ public function getRelationData() $stack[$name]['modelClass'] = $rel->modelClass; $stack[$name]['link'] = $rel->link; $stack[$name]['via'] = $rel->via; + $stack[$name]['where'] = []; + + if (!empty($rel->on)) { + $stack[$name]['where'] = $rel->on; + } + + if (!empty($rel->where)) { + $stack[$name]['where'] = $rel->where; + } } } else { $ARMethods = get_class_methods('\yii\db\ActiveRecord'); @@ -510,6 +530,15 @@ public function getRelationData() $stack[$name]['modelClass'] = $rel->modelClass; $stack[$name]['link'] = $rel->link; $stack[$name]['via'] = $rel->via; + $stack[$name]['where'] = []; + + if (!empty($rel->on)) { + $stack[$name]['where'] = $rel->on; + } + + if (!empty($rel->where)) { + $stack[$name]['where'] = $rel->where; + } } } catch (\Exception $exc) { //if method name can't be called, From 2192d3e16ec3ebb01d46bb907e498db215d91c3d Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Mon, 12 Feb 2018 02:16:13 +0530 Subject: [PATCH 11/22] Minor fix --- RelationTrait.php | 1 - 1 file changed, 1 deletion(-) diff --git a/RelationTrait.php b/RelationTrait.php index 41c0467..0e01c58 100644 --- a/RelationTrait.php +++ b/RelationTrait.php @@ -288,7 +288,6 @@ public function saveAll($skippedRelations = []) } } } - if (!$isNewRecord) { //No Children left $relAvail = array_keys($this->relatedRecords); From 551a47e2ee9c1a104128314bb51e0aad77147ff1 Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Mon, 12 Feb 2018 02:28:42 +0530 Subject: [PATCH 12/22] Minor fix --- RelationTrait.php | 1 + 1 file changed, 1 insertion(+) diff --git a/RelationTrait.php b/RelationTrait.php index 0e01c58..41c0467 100644 --- a/RelationTrait.php +++ b/RelationTrait.php @@ -288,6 +288,7 @@ public function saveAll($skippedRelations = []) } } } + if (!$isNewRecord) { //No Children left $relAvail = array_keys($this->relatedRecords); From 98ab3c8b6b0a230b15375d481a098851d36b280e Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Wed, 14 Feb 2018 20:23:46 +0530 Subject: [PATCH 13/22] Minor fix --- RelationTrait.php | 114 +++++++++++++++++++++++----------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/RelationTrait.php b/RelationTrait.php index 41c0467..3df8a8d 100644 --- a/RelationTrait.php +++ b/RelationTrait.php @@ -289,63 +289,63 @@ public function saveAll($skippedRelations = []) } } - if (!$isNewRecord) { - //No Children left - $relAvail = array_keys($this->relatedRecords); - $relData = $this->getRelationData(); - $allRel = array_keys($relData); - $noChildren = array_diff($allRel, $relAvail); - - foreach ($noChildren as $relName) { - /* @var $relModel ActiveRecord */ - if (empty($relData[$relName]['via']) && !in_array($relName, $skippedRelations)) { - $relModel = new $relData[$relName]['modelClass']; - $condition = []; - $isManyMany = count($relModel->primaryKey()) > 1; - if ($isManyMany) { - foreach ($relData[$relName]['link'] as $k => $v) { - $condition[$k] = $this->$v; - } - if (!empty($relData[$relName]['where'])) { - foreach ($relData[$relName]['where'] as $k => $v) { - $condition[$k] = $v; - } - } - try { - if ($isSoftDelete) { - $relModel->updateAll($this->_rt_softdelete, ['and', $condition]); - } else { - $relModel->deleteAll(['and', $condition]); - } - } catch (IntegrityException $exc) { - $this->addError($relData[$relName]['name'], Yii::t('mtrelt', "Data can't be deleted because it's still used by another data.")); - $error = true; - } - } else { - if ($relData[$relName]['ismultiple']) { - foreach ($relData[$relName]['link'] as $k => $v) { - $condition[$k] = $this->$v; - } - if (!empty($relData[$relName]['where'])) { - foreach ($relData[$relName]['where'] as $k => $v) { - $condition[$k] = $v; - } - } - try { - if ($isSoftDelete) { - $relModel->updateAll($this->_rt_softdelete, ['and', $condition]); - } else { - $relModel->deleteAll(['and', $condition]); - } - } catch (IntegrityException $exc) { - $this->addError($relData[$relName]['name'], Yii::t('mtrelt', "Data can't be deleted because it's still used by another data.")); - $error = true; - } - } - } - } - } - } +// if (!$isNewRecord) { +// //No Children left +// $relAvail = array_keys($this->relatedRecords); +// $relData = $this->getRelationData(); +// $allRel = array_keys($relData); +// $noChildren = array_diff($allRel, $relAvail); +// +// foreach ($noChildren as $relName) { +// /* @var $relModel ActiveRecord */ +// if (empty($relData[$relName]['via']) && !in_array($relName, $skippedRelations)) { +// $relModel = new $relData[$relName]['modelClass']; +// $condition = []; +// $isManyMany = count($relModel->primaryKey()) > 1; +// if ($isManyMany) { +// foreach ($relData[$relName]['link'] as $k => $v) { +// $condition[$k] = $this->$v; +// } +// if (!empty($relData[$relName]['where'])) { +// foreach ($relData[$relName]['where'] as $k => $v) { +// $condition[$k] = $v; +// } +// } +// try { +// if ($isSoftDelete) { +// $relModel->updateAll($this->_rt_softdelete, ['and', $condition]); +// } else { +// $relModel->deleteAll(['and', $condition]); +// } +// } catch (IntegrityException $exc) { +// $this->addError($relData[$relName]['name'], Yii::t('mtrelt', "Data can't be deleted because it's still used by another data.")); +// $error = true; +// } +// } else { +// if ($relData[$relName]['ismultiple']) { +// foreach ($relData[$relName]['link'] as $k => $v) { +// $condition[$k] = $this->$v; +// } +// if (!empty($relData[$relName]['where'])) { +// foreach ($relData[$relName]['where'] as $k => $v) { +// $condition[$k] = $v; +// } +// } +// try { +// if ($isSoftDelete) { +// $relModel->updateAll($this->_rt_softdelete, ['and', $condition]); +// } else { +// $relModel->deleteAll(['and', $condition]); +// } +// } catch (IntegrityException $exc) { +// $this->addError($relData[$relName]['name'], Yii::t('mtrelt', "Data can't be deleted because it's still used by another data.")); +// $error = true; +// } +// } +// } +// } +// } +// } if ($error) { $trans->rollback(); From 7e2e5e15cbc9a23676a926fb715ef81d7cd1a8e3 Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Wed, 14 Feb 2018 20:45:22 +0530 Subject: [PATCH 14/22] Minor fix --- RelationTrait.php | 1 + 1 file changed, 1 insertion(+) diff --git a/RelationTrait.php b/RelationTrait.php index 3df8a8d..2b84f33 100644 --- a/RelationTrait.php +++ b/RelationTrait.php @@ -347,6 +347,7 @@ public function saveAll($skippedRelations = []) // } // } + if ($error) { $trans->rollback(); $this->isNewRecord = $isNewRecord; From 2aa986e3232938caf5de8ab5a8d0fcf16d2564c2 Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Wed, 14 Feb 2018 20:52:45 +0530 Subject: [PATCH 15/22] Minor fix tag issue --- RelationTrait.php | 1 - 1 file changed, 1 deletion(-) diff --git a/RelationTrait.php b/RelationTrait.php index 2b84f33..3df8a8d 100644 --- a/RelationTrait.php +++ b/RelationTrait.php @@ -347,7 +347,6 @@ public function saveAll($skippedRelations = []) // } // } - if ($error) { $trans->rollback(); $this->isNewRecord = $isNewRecord; From 159ddf75f853113d81fb0ce12a20db24f8b6d619 Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Mon, 6 Jan 2025 14:48:22 +0530 Subject: [PATCH 16/22] =?UTF-8?q?=F0=9F=94=A7=20(gitignore):=20add=20.idea?= =?UTF-8?q?=20and=20vendor=20to=20ignore=20IDE=20and=20dependency=20files?= =?UTF-8?q?=20=F0=9F=93=9D=20(README.md):=20update=20documentation=20for?= =?UTF-8?q?=20clarity=20and=20additional=20features=20=E2=99=BB=EF=B8=8F?= =?UTF-8?q?=20(RelationTrait.php):=20refactor=20code=20for=20better=20read?= =?UTF-8?q?ability=20and=20maintainability=20=E2=AC=86=EF=B8=8F=20(compose?= =?UTF-8?q?r.json):=20update=20PHP=20version=20requirement=20and=20add=20a?= =?UTF-8?q?sset-packagist=20repository?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +- README.md | 298 +++++--------- RelationTrait.php | 1007 ++++++++++++++++++++++++--------------------- composer.json | 76 ++-- composer.lock | 363 +++++++++------- 5 files changed, 904 insertions(+), 844 deletions(-) diff --git a/.gitignore b/.gitignore index c3adeeb..2a69cd4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -/nbproject/* \ No newline at end of file +/nbproject/* +.idea +vendor \ No newline at end of file diff --git a/README.md b/README.md index a5ebdeb..67b7214 100644 --- a/README.md +++ b/README.md @@ -1,243 +1,151 @@ # yii2-relation-trait -#### I am not the creator of this extension, I have made a bug fix and made a pull request. I am just publishing this for my own usage. Feel free to use it. +> **Note**: This is **not** the official extension by [@mootensai](https://github.com/mootensai). +> I am not the creator of the original extension. I have made bug fixes and improvements that suit my use case. +> Feel free to use it or refer to the official package +> at [mootensai/yii2-relation-trait](https://github.com/mootensai/yii2-relation-trait). -#### All thanks to @mootensai for creating the extension. +Yii 2 Models add functionality for loading related models via `loadAll($POST)` and transactional saving via +`saveAll()`. +It also supports **soft delete** and **soft restore** features. -#### This is not to replace the package maintained by @mootensai +Works best with [mootensai/yii2-enhanced-gii](https://github.com/mootensai/yii2-enhanced-gii). -Yii 2 Models add functionality for load with relation (loadAll($POST)), & transactional save with relation (saveAll()) +## Badges -PLUS soft delete/restore feature! - -Best work with [mootensai/yii2-enhanced-gii](https://github.com/mootensai/yii2-enhanced-gii) - -[![Latest Stable Version](https://poser.pugx.org/mootensai/yii2-relation-trait/v/stable)](https://packagist.org/packages/mootensai/yii2-relation-trait) -[![License](https://poser.pugx.org/mootensai/yii2-relation-trait/license)](https://packagist.org/packages/mootensai/yii2-relation-trait) -[![Total Downloads](https://img.shields.io/packagist/dt/mootensai/yii2-relation-trait.svg?style=flat-square)](https://packagist.org/packages/mootensai/yii2-relation-trait) -[![Monthly Downloads](https://poser.pugx.org/mootensai/yii2-relation-trait/d/monthly)](https://packagist.org/packages/mootensai/yii2-relation-trait) -[![Daily Downloads](https://poser.pugx.org/mootensai/yii2-relation-trait/d/daily)](https://packagist.org/packages/mootensai/yii2-relation-trait) -[![Join the chat at https://gitter.im/mootensai/yii2-relation-trait](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mootensai/yii2-relation-trait?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -## Support - -[![Support via Gratipay](https://cdn.rawgit.com/gratipay/gratipay-badge/2.3.0/dist/gratipay.svg)](https://gratipay.com/mootensai/) - -https://www.paypal.me/yohanesc - -Endorse me on LinkedIn - -https://www.linkedin.com/in/yohanes-candrajaya-b68394102/ +[![Latest Stable Version](https://poser.pugx.org/deadmantfa/yii2-relation-trait/v/stable)](https://packagist.org/packages/deadmantfa/yii2-relation-trait) +[![License](https://poser.pugx.org/deadmantfa/yii2-relation-trait/license)](https://packagist.org/packages/deadmantfa/yii2-relation-trait) +[![Total Downloads](https://img.shields.io/packagist/dt/deadmantfa/yii2-relation-trait.svg?style=flat-square)](https://packagist.org/packages/deadmantfa/yii2-relation-trait) +[![Monthly Downloads](https://poser.pugx.org/deadmantfa/yii2-relation-trait/d/monthly)](https://packagist.org/packages/deadmantfa/yii2-relation-trait) +[![Daily Downloads](https://poser.pugx.org/deadmantfa/yii2-relation-trait/d/daily)](https://packagist.org/packages/deadmantfa/yii2-relation-trait) ## Installation -The preferred way to install this extension is through [composer](http://getcomposer.org/download/). +The preferred way to install this extension is through [Composer](http://getcomposer.org/download/). Either run ```bash -$ composer require 'deadmantfa/yii2-relation-trait:dev-master' +composer require deadmantfa/yii2-relation-trait:dev-master ``` or add -``` +```php "deadmantfa/yii2-relation-trait": "*" ``` -to the `require` section of your `composer.json` file. - +to the require section of your application's ```composer.json``` file. -## Usage At Model +## Usage in the Model ```php -class MyModel extends ActiveRecord{ - use \deadmantfa\relation\RelationTrait; -} -``` +use deadmantfa\relation\RelationTrait; -## Array Input & Usage At Controller +class MyModel extends \yii\db\ActiveRecord +{ + use RelationTrait; -It takes a normal array of POST. This is the example -```php -Array ( - $_POST['ParentClass'] => Array - ( - [attr1] => value1 - [attr2] => value2 - // has many - [relationName] => Array - ( - [0] => Array - ( - [relAttr] => relValue1 - ) - [1] => Array - ( - [relAttr] => relValue1 - ) - ) - // has one - [relationName] => Array - ( - [relAttr1] => relValue1 - [relAttr2] => relValue2 - ) - ) -) - -OR - -Array ( - $_POST['ParentClass'] => ['attr1' => 'value1','attr2' => 'value2'], - // Has One - $_POST['RelatedClass'] => ['relAttr1' => 'value1','relAttr2' => 'value2'], - // Has Many - $_POST['RelatedClass'] => Array - ( - [0] => Array - ( - [attr1] => value1 - [attr2] => value2 - ) - [1] => Array - ( - [attr1] => value1 - [attr2] => value2 - ) - ) -) -``` - -```php -// sample at controller -if($model->loadAll(Yii:$app->request->post()) && $model->saveAll()){ - return $this->redirect(['view', 'id' => $model->id, 'created' => $model->created]); + // ... } ``` -# Features +## Controller Usage -## Array Output +The extension expects a **normal array of POST** data. For example: ```php -// I use this to send model & related through JSON / Serialize -print_r($model->getAttributesWithRelatedAsPost()); -``` - -``` -Array -( - [MainClass] => Array - ( - [attr1] => value1 - [attr2] => value2 - ) - - [RelatedClass] => Array - ( - [0] => Array - ( - [attr1] => value1 - [attr2] => value2 - ) - ) - -) -``` +[ + $_POST['ParentClass'] => [ + 'attr1' => 'value1', + 'attr2' => 'value2', + // Has many + 'relationName' => [ + [ 'relAttr' => 'relValue1' ], + [ 'relAttr' => 'relValue2' ] + ], + // Has one + 'relationName' => [ + 'relAttr1' => 'relValue1', + 'relAttr2' => 'relValue2' + ] + ] +]; +``` + +In your controller: ```php -print_r($model->getAttributesWithRelated()); -``` - -``` -Array -( - [attr1] => value1 - [attr2] => value2 - [relationName] => Array - ( - [0] => Array - ( - [attr1] => value1 - [attr2] => value2 - ) - ) -) -``` - -## Use Transaction - -So your data will be atomic -(see : http://en.wikipedia.org/wiki/ACID) - -## Use Normal Save - -So your behaviors still works - -## Add Validation At Main Model - -```php -$form->errorSummary($model); -``` - -will give you - -``` -<> #<> : <> -My Related Model #1 : Attribute is required +$model = new ParentClass(); +if ($model->loadAll(Yii::$app->request->post()) && $model->saveAll()) { + return $this->redirect(['view', 'id' => $model->id]); +} ``` -## It Works On Auto Incremental PK Or Not (I Have Tried Use UUID) - -See here if you want to use my behavior : - -https://github.com/mootensai/yii2-uuid-behavior - -## Soft Delete - -Add this line to your Model to enable soft delete +Features + +1. Transaction Support + a. Your data changes are atomic (ACID compliant). +2. Normal ```save()``` + a. Behaviors still work as usual since it’s built on top of Yii’s ```ActiveRecord```. +3. Validation + a. Errors from related models appear via ```errorSummary()```, e.g. + ```text + MyRelatedClass #2: [Error message] + ``` +4. UUID or Auto-Increment + Works with any PK strategy, + including [mootensai/yii2-uuid-behavior](https://github.com/mootensai/yii2-uuid-behavior). +5. Soft Delete + By defining ```$_rt_softdelete``` in your model constructor (and ```$_rt_softrestore``` for restoring), you can + softly mark rows as deleted instead of physically removing them. + ```php + private $_rt_softdelete; + private $_rt_softrestore; + + public function __construct($config = []) + { + parent::__construct($config); + + $this->_rt_softdelete = [ + 'is_deleted' => 1, + 'deleted_by' => Yii::$app->user->id, + 'deleted_at' => date('Y-m-d H:i:s'), + ]; + + $this->_rt_softrestore = [ + 'is_deleted' => 0, + 'deleted_by' => null, + 'deleted_at' => null, + ]; + } + ``` + +## Array Outputs ```php -private $_rt_softdelete; - -function __construct(){ - $this->_rt_softdelete = [ - '' => - // multiple row marker column example - 'isdeleted' => 1, - 'deleted_by' => \Yii::$app->user->id, - 'deleted_at' => date('Y-m-d H:i:s') - ]; -} +print_r($model->getAttributesWithRelatedAsPost()); ``` -Add this line to your Model to enable soft restore +Produces a POST-like structure with the main model and related arrays. ```php -private $_rt_softrestore; - -function __construct(){ - $this->_rt_softrestore = [ - '' => - // multiple row marker column example - 'isdeleted' => 0, - 'deleted_by' => 0, - 'deleted_at' => 'NULL' - ]; -} +print_r($model->getAttributesWithRelated()); ``` -### Should work on Yii's supported DB - -It use all Yii's Active Query or Active Record to execute DB command - - -### I'm open for any improvement -Please create issue if you got a problem or an idea for enhancement +Produces a nested structure under ```[relationName] => [...]```. -#### ~ SDG ~ +## Contributing or Reporting Issues +Please open an [issue](https://github.com/deadmantfa/yii2-relation-trait/pulls) or submit a PR if you find a bug or have +an improvement idea. +--- +### Disclaimer +This package is a **fork** or an alternative +to [mootensai/yii2-relation-trait](https://github.com/mootensai/yii2-relation-trait). +**All credit** to [@mootensai](https://github.com/mootensai) for the initial code. +**This is not meant to replace** the original package but rather provide bug fixes and enhancements under a different +namespace. \ No newline at end of file diff --git a/RelationTrait.php b/RelationTrait.php index 3df8a8d..f3e1c32 100644 --- a/RelationTrait.php +++ b/RelationTrait.php @@ -1,362 +1,431 @@ - * @since 1.0 - */ +declare(strict_types=1); namespace deadmantfa\relation; +use ReflectionClass; +use Throwable; use Yii; +use yii\base\Model; use yii\db\ActiveQuery; -use \yii\db\ActiveRecord; -use \yii\db\Exception; +use yii\db\ActiveQueryInterface; +use yii\db\ActiveRecord; +use yii\db\Exception; use yii\db\IntegrityException; -use \yii\helpers\Inflector; -use \yii\helpers\StringHelper; use yii\helpers\ArrayHelper; +use yii\helpers\Inflector; +use yii\helpers\StringHelper; +use yii\i18n\PhpMessageSource; -/* - * add this line to your Model to enable soft delete - * - * private $_rt_softdelete; - * - * function __construct(){ - * $this->_rt_softdelete = [ - * '' => - * // multiple row marker column example - * 'isdeleted' => 1, - * 'deleted_by' => \Yii::$app->user->id, - * 'deleted_at' => date('Y-m-d H:i:s') - * ]; - * } - * add this line to your Model to enable soft restore - * private $_rt_softrestore; +/** + * Trait RelationTrait * - * function __construct(){ - * $this->_rt_softrestore = [ - * '' => - * // multiple row marker column example - * 'isdeleted' => 0, - * 'deleted_by' => 0, - * 'deleted_at' => 'NULL' - * ]; - * } + * Provides methods for mass-loading related models via POST data and saving/deleting + * them in a single transaction. Includes optional soft-delete/restore functionality. */ - trait RelationTrait { - /** - * Load all attribute including related attribute - * @param $POST - * @param array $skippedRelations + * Load model attributes including related attributes from POST data. + * + * @param array $POST The POST data (likely Yii::$app->request->post()). + * @param array $skippedRelations Relations to exclude from load. * @return bool */ - public function loadAll($POST, $skippedRelations = []) + public function loadAll(array $POST, array $skippedRelations = []): bool { - if ($this->load($POST)) { - $shortName = StringHelper::basename(get_class($this)); - $relData = $this->getRelationData(); - foreach ($POST as $model => $attr) { - if (is_array($attr)) { - if ($model == $shortName) { - foreach ($attr as $relName => $relAttr) { - if (is_array($relAttr)) { - $isHasMany = !ArrayHelper::isAssociative($relAttr); - if (in_array($relName, $skippedRelations) || !array_key_exists($relName, $relData)) { - continue; - } + if (!$this->load($POST)) { + return false; + } - $this->loadToRelation($isHasMany, $relName, $relAttr); - } - } - } else { - $isHasMany = is_array($attr) && is_array(current($attr)); - $relName = ($isHasMany) ? lcfirst(Inflector::pluralize($model)) : lcfirst($model); - if (in_array($relName, $skippedRelations) || !array_key_exists($relName, $relData)) { - continue; - } + $shortName = StringHelper::basename(static::class); + $relData = $this->getRelationData(); + + // We assume the top-level array in $POST is named after $shortName or the related classes + foreach ($POST as $model => $attr) { + if (!is_array($attr)) { + continue; + } - $this->loadToRelation($isHasMany, $relName, $attr); + // If the POST key matches our model short name + if ($model === $shortName) { + // e.g. $POST['MyModel'] => ['someRelation' => [...], 'anotherRelation' => [...]] + foreach ($attr as $relName => $relAttr) { + if (!is_array($relAttr)) { + continue; + } + $isHasMany = !ArrayHelper::isAssociative($relAttr); + if (in_array($relName, $skippedRelations, true) || !array_key_exists($relName, $relData)) { + continue; } + $this->loadToRelation($isHasMany, $relName, $relAttr); + } + } else { + // If $model is the name of a related class, guess that the relation name + // is pluralized or singularized version of $model. + $isHasMany = is_array(current($attr)); + $relName = $isHasMany + ? lcfirst(Inflector::pluralize($model)) + : lcfirst($model); + + if (in_array($relName, $skippedRelations, true) || !array_key_exists($relName, $relData)) { + continue; } + $this->loadToRelation($isHasMany, $relName, $attr); } - return true; } - return false; + + return true; } /** - * Refactored from loadAll() function - * @param $isHasMany - * @param $relName - * @param $v + * Retrieves information about all relations defined in this model by scanning: + * - 'relationNames()' method if defined in the model (must return an array of relation names as strings), or + * - reflection for 'getXYZ()' methods returning an ActiveQueryInterface. + * + * @return array + */ + public function getRelationData(): array + { + $stack = []; + + // If the model has a custom "relationNames()" method, use that to retrieve relation names + if (method_exists($this, 'relationNames')) { + // Expecting something like: ['relationA', 'relationB', ...] + $names = $this->relationNames(); + foreach ($names as $name) { + /** @var ActiveQuery $rel */ + $rel = $this->getRelation($name); + $stack[$name] = [ + 'name' => $name, + 'method' => 'get' . ucfirst($name), + 'ismultiple' => $rel->multiple, + 'modelClass' => $rel->modelClass, + 'link' => $rel->link, + 'via' => $rel->via, + ]; + } + return $stack; + } + + // Otherwise, reflect on all getSomething() methods + $ARMethods = get_class_methods(ActiveRecord::class); + $modelMethods = get_class_methods(Model::class); + $reflection = new ReflectionClass($this); + + foreach ($reflection->getMethods() as $method) { + $methodName = $method->getName(); + + // Skip parent or irrelevant methods + if (in_array($methodName, $ARMethods, true) || + in_array($methodName, $modelMethods, true) || + in_array($methodName, [ + 'getRelationData', + 'getAttributesWithRelatedAsPost', + 'getAttributesWithRelated', + 'getRelatedRecordsTree', + ], true) + ) { + continue; + } + + if (strpos($methodName, 'get') !== 0) { + continue; + } + if ($method->getNumberOfParameters() > 0) { + continue; + } + + try { + $rel = call_user_func([$this, $methodName]); + if (!$rel instanceof ActiveQueryInterface) { + continue; + } + $propName = lcfirst(preg_replace('/^get/', '', $methodName)); + $stack[$propName] = [ + 'name' => $propName, + 'method' => $methodName, + 'ismultiple' => $rel->multiple, + 'modelClass' => $rel->modelClass, + 'link' => $rel->link, + 'via' => $rel->via, + ]; + } catch (Throwable $exc) { + // ignore + } + } + + return $stack; + } + + /** + * Load array data into a single relation (HasOne, HasMany, or ManyMany). + * + * @param bool $isHasMany Whether this relation is plural (HasMany / ManyMany). + * @param string $relName The relation name in the AR model. + * @param array $v The data array for that relation. * @return bool */ - private function loadToRelation($isHasMany, $relName, $v) + private function loadToRelation(bool $isHasMany, string $relName, array $v): bool { - /* @var $AQ ActiveQuery */ - /* @var $this ActiveRecord */ - /* @var $relObj ActiveRecord */ + /** @var ActiveRecord $this */ $AQ = $this->getRelation($relName); - /* @var $relModelClass ActiveRecord */ $relModelClass = $AQ->modelClass; $relPKAttr = $relModelClass::primaryKey(); - $isManyMany = count($relPKAttr) > 1; + // If there's more than one column in the PK array, we consider it ManyMany. + // (In some advanced pivot scenarios, you might refine this logic further.) + $isManyMany = (count($relPKAttr) > 1); + // Many-to-many if ($isManyMany) { $container = []; foreach ($v as $relPost) { - if (array_filter($relPost)) { - $condition = []; - $condition[$relPKAttr[0]] = $this->primaryKey; - foreach ($relPost as $relAttr => $relAttrVal) { - if (in_array($relAttr, $relPKAttr)) { - $condition[$relAttr] = $relAttrVal; - } - } - $relObj = $relModelClass::findOne($condition); - if (is_null($relObj)) { - $relObj = new $relModelClass; + if (!array_filter($relPost)) { + continue; + } + // Build condition for the pivot table + // Make sure $relPKAttr[0] exists + if (!isset($relPKAttr[0])) { + // No well-defined first PK attribute => skip or handle otherwise + continue; + } + $condition = [$relPKAttr[0] => $this->primaryKey]; + + foreach ($relPost as $relAttr => $relAttrVal) { + if (in_array($relAttr, $relPKAttr, true)) { + $condition[$relAttr] = $relAttrVal; } - $relObj->load($relPost, ''); - $container[] = $relObj; } + + $relObj = $relModelClass::findOne($condition); + if ($relObj === null) { + $relObj = new $relModelClass(); + } + $relObj->load($relPost, ''); + $container[] = $relObj; } $this->populateRelation($relName, $container); - } else if ($isHasMany) { + return true; + } + + // HasMany + if ($isHasMany) { $container = []; foreach ($v as $relPost) { - if (array_filter($relPost)) { - /* @var $relObj ActiveRecord */ - $relObj = (empty($relPost[$relPKAttr[0]])) ? new $relModelClass() : $relModelClass::findOne($relPost[$relPKAttr[0]]); - if (is_null($relObj)) { - $relObj = new $relModelClass(); - } - $relObj->load($relPost, ''); - $container[] = $relObj; + if (!array_filter($relPost)) { + continue; + } + $primaryKeyVal = $relPost[$relPKAttr[0]] ?? null; + $relObj = null; + if ($primaryKeyVal) { + $relObj = $relModelClass::findOne($primaryKeyVal); } + if ($relObj === null) { + $relObj = new $relModelClass(); + } + $relObj->load($relPost, ''); + $container[] = $relObj; } $this->populateRelation($relName, $container); - } else { - $relObj = (empty($v[$relPKAttr[0]])) ? new $relModelClass : $relModelClass::findOne($v[$relPKAttr[0]]); - $relObj->load($v, ''); - $this->populateRelation($relName, $relObj); + return true; } + + // HasOne + $primaryKeyVal = $v[$relPKAttr[0]] ?? null; + $relObj = null; + if ($primaryKeyVal) { + $relObj = $relModelClass::findOne($primaryKeyVal); + } + if ($relObj === null) { + $relObj = new $relModelClass(); + } + $relObj->load($v, ''); + $this->populateRelation($relName, $relObj); + return true; } /** - * Save model including all related model already loaded - * @param array $skippedRelations + * Save model and all related records in a transaction. + * Optionally uses soft-delete if configured. + * + * @param array $skippedRelations Relations to exclude from save. * @return bool * @throws Exception */ - public function saveAll($skippedRelations = []) + public function saveAll(array $skippedRelations = []): bool { - /* @var $this ActiveRecord */ + /** @var ActiveRecord $this */ $db = $this->getDb(); $trans = $db->beginTransaction(); $isNewRecord = $this->isNewRecord; $isSoftDelete = isset($this->_rt_softdelete); + $error = false; + try { - if ($this->save()) { - $error = false; - if (!empty($this->relatedRecords)) { - /* @var $records ActiveRecord | ActiveRecord[] */ - foreach ($this->relatedRecords as $name => $records) { - if (in_array($name, $skippedRelations)) + if (!$this->save()) { + $trans->rollBack(); + return false; + } + + // Save any loaded related records + if (!empty($this->relatedRecords)) { + foreach ($this->relatedRecords as $name => $records) { + if (in_array($name, $skippedRelations, true)) { + continue; + } + + /** @var ActiveQuery $AQ */ + $AQ = $this->getRelation($name); + $link = $AQ->link; + if (empty($records)) { + continue; + } + + if ($AQ->multiple) { + // ManyMany or HasMany + $firstRelModel = is_array($records) ? reset($records) : null; + if (!$firstRelModel instanceof ActiveRecord) { continue; + } + $relPKAttr = $firstRelModel->primaryKey(); + $isManyMany = (count($relPKAttr) > 1); - $AQ = $this->getRelation($name); - $link = $AQ->link; - if (!empty($records)) { - $notDeletedPK = []; - $notDeletedFK = []; - $relPKAttr = ($AQ->multiple) ? $records[0]->primaryKey() : $records->primaryKey(); - $isManyMany = (count($relPKAttr) > 1); - $conditions = []; - if (!empty($AQ->on)) { - $conditions[] = $AQ->on; - } + // Collect PK & FK for leftover deletion + $notDeletedPK = []; + $notDeletedFK = []; - if (!empty($AQ->where)) { - $conditions[] = $AQ->where; + foreach ($records as $index => $relModel) { + if (!$relModel instanceof ActiveRecord) { + continue; + } + // Ensure this child's FK references the parent + foreach ($link as $key => $value) { + $relModel->{$key} = $this->{$value}; + $notDeletedFK[$key] = $this->{$value}; } - if ($AQ->multiple) { - /* @var $relModel ActiveRecord */ - foreach ($records as $index => $relModel) { - foreach ($link as $key => $value) { - $relModel->$key = $this->$value; - $notDeletedFK[$key] = $this->$value; - } - //GET PK OF REL MODEL - if ($isManyMany) { - $mainPK = array_keys($link)[0]; - foreach ($relModel->primaryKey as $attr => $value) { - if ($attr != $mainPK) { - $notDeletedPK[$attr][] = $value; - } - } - } else { - $notDeletedPK[] = $relModel->primaryKey; + // Mark PK for not-deleting + if ($isManyMany) { + $mainPK = array_key_first($link); + // In a truly multi-column pivot, you might need more robust logic. + foreach ($relModel->primaryKey as $attr => $val) { + if ($attr !== $mainPK) { + $notDeletedPK[$attr][] = $val; } - } + } else { + $notDeletedPK[] = $relModel->primaryKey; + } + } - if (!$isNewRecord) { - //DELETE WITH 'NOT IN' PK MODEL & REL MODEL - if ($isManyMany) { - // Many Many - $query = ['and', $notDeletedFK]; - foreach ($notDeletedPK as $attr => $value) { - $notIn = ['not in', $attr, $value]; - array_push($query, $notIn); - } - if (!empty($conditions)) { - foreach ($conditions as $condition) { - array_push($query, $condition); - } - } - try { - if ($isSoftDelete) { - $relModel->updateAll($this->_rt_softdelete, $query); - } else { - $relModel->deleteAll($query); - } - } catch (IntegrityException $exc) { - $this->addError($name, "Data can't be deleted because it's still used by another data."); - $error = true; - } - } else { - // Has Many - $query = ['and', $notDeletedFK, ['not in', $relPKAttr[0], $notDeletedPK]]; - if (!empty($conditions)) { - foreach ($conditions as $condition) { - array_push($query, $condition); - } - } - if (!empty($notDeletedPK)) { - try { - if ($isSoftDelete) { - $relModel->updateAll($this->_rt_softdelete, $query); - } else { - $relModel->deleteAll($query); - } - } catch (IntegrityException $exc) { - $this->addError($name, "Data can't be deleted because it's still used by another data."); - $error = true; - } - } - } - } + // For existing parent, remove leftover children + if (!$isNewRecord) { + $relationLabel = Yii::t('app', Inflector::camel2words(StringHelper::basename($AQ->modelClass))); + $relModel = $firstRelModel; - foreach ($records as $index => $relModel) { - $relSave = $relModel->save(); - - if (!$relSave || !empty($relModel->errors)) { - $relModelWords = Yii::t('app', Inflector::camel2words(StringHelper::basename($AQ->modelClass))); - $index++; - foreach ($relModel->errors as $validation) { - foreach ($validation as $errorMsg) { - $this->addError($name, "$relModelWords #$index : $errorMsg"); - } - } - $error = true; - } + if ($isManyMany) { + // ManyMany leftover cleanup + $query = ['and', $notDeletedFK]; + foreach ($notDeletedPK as $attr => $values) { + $query[] = ['not in', $attr, $values]; } + $this->safeDeleteOrSoftDelete( + $relModel, + $isSoftDelete ? $this->_rt_softdelete : [], + $query, + $relationLabel, + $name, + $error + ); } else { - //Has One - foreach ($link as $key => $value) { - $records->$key = $this->$value; + // HasMany leftover cleanup + $primaryColumn = $relPKAttr[0] ?? null; + if ($primaryColumn !== null && !empty($notDeletedPK)) { + $query = ['and', $notDeletedFK, ['not in', $primaryColumn, $notDeletedPK]]; + $this->safeDeleteOrSoftDelete( + $relModel, + $isSoftDelete ? $this->_rt_softdelete : [], + $query, + $relationLabel, + $name, + $error + ); + } + } + } + + // Now save each related record + foreach ($records as $index => $relModel) { + if (!$relModel->save() || !empty($relModel->errors)) { + $relModelWords = Yii::t('app', Inflector::camel2words(StringHelper::basename($AQ->modelClass))); + $idx = $index + 1; + foreach ($relModel->errors as $validation) { + foreach ($validation as $errorMsg) { + $this->addError($name, "$relModelWords #$idx : $errorMsg"); + } } - $relSave = $records->save(); - if (!$relSave || !empty($records->errors)) { - $recordsWords = Yii::t('app', Inflector::camel2words(StringHelper::basename($AQ->modelClass))); - foreach ($records->errors as $validation) { - foreach ($validation as $errorMsg) { - $this->addError($name, "$recordsWords : $errorMsg"); - } + $error = true; + } + } + } else { + // HasOne + if (!is_array($records) && $records instanceof ActiveRecord) { + $relModel = $records; + foreach ($link as $key => $value) { + $relModel->{$key} = $this->{$value}; + } + if (!$relModel->save() || !empty($relModel->errors)) { + $recordsWords = Yii::t('app', Inflector::camel2words(StringHelper::basename($AQ->modelClass))); + foreach ($relModel->errors as $validation) { + foreach ($validation as $errorMsg) { + $this->addError($name, "$recordsWords : $errorMsg"); } - $error = true; } + $error = true; } } } } + } + + // Remove children for relations not in $this->relatedRecords + $relAvail = array_keys($this->relatedRecords); + $relData = $this->getRelationData(); + $allRel = array_keys($relData); + $noChildren = array_diff($allRel, $relAvail); -// if (!$isNewRecord) { -// //No Children left -// $relAvail = array_keys($this->relatedRecords); -// $relData = $this->getRelationData(); -// $allRel = array_keys($relData); -// $noChildren = array_diff($allRel, $relAvail); -// -// foreach ($noChildren as $relName) { -// /* @var $relModel ActiveRecord */ -// if (empty($relData[$relName]['via']) && !in_array($relName, $skippedRelations)) { -// $relModel = new $relData[$relName]['modelClass']; -// $condition = []; -// $isManyMany = count($relModel->primaryKey()) > 1; -// if ($isManyMany) { -// foreach ($relData[$relName]['link'] as $k => $v) { -// $condition[$k] = $this->$v; -// } -// if (!empty($relData[$relName]['where'])) { -// foreach ($relData[$relName]['where'] as $k => $v) { -// $condition[$k] = $v; -// } -// } -// try { -// if ($isSoftDelete) { -// $relModel->updateAll($this->_rt_softdelete, ['and', $condition]); -// } else { -// $relModel->deleteAll(['and', $condition]); -// } -// } catch (IntegrityException $exc) { -// $this->addError($relData[$relName]['name'], Yii::t('mtrelt', "Data can't be deleted because it's still used by another data.")); -// $error = true; -// } -// } else { -// if ($relData[$relName]['ismultiple']) { -// foreach ($relData[$relName]['link'] as $k => $v) { -// $condition[$k] = $this->$v; -// } -// if (!empty($relData[$relName]['where'])) { -// foreach ($relData[$relName]['where'] as $k => $v) { -// $condition[$k] = $v; -// } -// } -// try { -// if ($isSoftDelete) { -// $relModel->updateAll($this->_rt_softdelete, ['and', $condition]); -// } else { -// $relModel->deleteAll(['and', $condition]); -// } -// } catch (IntegrityException $exc) { -// $this->addError($relData[$relName]['name'], Yii::t('mtrelt', "Data can't be deleted because it's still used by another data.")); -// $error = true; -// } -// } -// } -// } -// } -// } - - if ($error) { - $trans->rollback(); - $this->isNewRecord = $isNewRecord; - return false; + foreach ($noChildren as $relName) { + if (!empty($relData[$relName]['via']) || + in_array($relName, $skippedRelations, true) + ) { + continue; } - $trans->commit(); - return true; - } else { + + $relModelClass = $relData[$relName]['modelClass']; + /** @var ActiveRecord $relModel */ + $relModel = new $relModelClass(); + $condition = $this->buildConditionFromLink($relData[$relName]['link']); + + $relationLabel = Inflector::camel2words(StringHelper::basename($relData[$relName]['modelClass'])); + $this->safeDeleteOrSoftDelete( + $relModel, + $isSoftDelete ? $this->_rt_softdelete : [], + ['and', $condition], + $relationLabel, + $relData[$relName]['name'], + $error + ); + } + + if ($error) { + $trans->rollBack(); + $this->isNewRecord = $isNewRecord; return false; } + + $trans->commit(); + return true; } catch (Exception $exc) { $trans->rollBack(); $this->isNewRecord = $isNewRecord; @@ -364,60 +433,145 @@ public function saveAll($skippedRelations = []) } } + /** + * Unified method to either "soft-delete" (updateAll) or physically delete (deleteAll), + * catching IntegrityException for foreign key constraints. + * + * @param ActiveRecord $modelClassOrInstance Model class or an instance used for static calls. + * @param array $softDeleteData The soft-delete attributes (if any). If empty, do hard delete. + * @param array $condition The query condition (e.g. ['and', [...]]). + * @param string $relationLabel Label for error messages. + * @param string $relationName The relation property name (for $this->addError). + * @param bool $errorRef This is passed by reference—set to true if an IntegrityException occurs. + */ + private function safeDeleteOrSoftDelete( + ActiveRecord $modelClassOrInstance, + array $softDeleteData, + array $condition, + string $relationLabel, + string $relationName, + bool &$errorRef + ): void + { + try { + if (!empty($softDeleteData)) { + $modelClassOrInstance->updateAll($softDeleteData, $condition); + } else { + $modelClassOrInstance->deleteAll($condition); + } + } catch (IntegrityException $exc) { + // Optionally append the DB error message for debugging + $dbMsg = $exc->getMessage(); + $errorMsg = Yii::t('app', "Data can't be deleted because it's still used by another data. DB says: {dbErr}", [ + 'dbErr' => $dbMsg, + ]); + $this->addError($relationName, "$relationLabel: $errorMsg"); + $errorRef = true; + } + } + + /** + * A small helper for building a 'where' condition array from a relation link array. + * + * For example, if $link = ['foreign_key_id' => 'id'], we build `['foreign_key_id' => $this->id]`. + * + * @param array $link + * @return array + */ + private function buildConditionFromLink(array $link): array + { + $condition = []; + foreach ($link as $key => $value) { + if (isset($this->{$value})) { + $condition[$key] = $this->{$value}; + } + } + return $condition; + } /** - * Deleted model row with all related records - * @param array $skippedRelations + * Delete this model and all related records. + * If soft-delete is configured, it will only mark them as deleted. + * + * @param array $skippedRelations Relations to exclude from deletion. * @return bool - * @throws Exception + * @throws Exception|Throwable */ - public function deleteWithRelated($skippedRelations = []) + public function deleteWithRelated(array $skippedRelations = []): bool { - /* @var $this ActiveRecord */ + /** @var ActiveRecord $this */ $db = $this->getDb(); $trans = $db->beginTransaction(); $isSoftDelete = isset($this->_rt_softdelete); + $error = false; + try { - $error = false; $relData = $this->getRelationData(); foreach ($relData as $data) { - $array = []; - if ($data['ismultiple'] && !in_array($data['name'], $skippedRelations)) { - $link = $data['link']; - if (count($this->{$data['name']})) { - foreach ($link as $key => $value) { - if (isset($this->$value)) { - $array[$key] = $this->$value; - } - } - if ($isSoftDelete) { - $error = !$this->{$data['name']}[0]->updateAll($this->_rt_softdelete, ['and', $array]); - } else { - $error = !$this->{$data['name']}[0]->deleteAll(['and', $array]); - } + if (!$data['ismultiple'] || in_array($data['name'], $skippedRelations, true)) { + continue; + } + $relationName = $data['name']; + + // If the relation is empty, no need to do anything + if (empty($this->{$relationName}) || !is_array($this->{$relationName})) { + continue; + } + + // Build condition + $condition = $this->buildConditionFromLink($data['link']); + if (empty($condition)) { + continue; + } + + /** @var ActiveRecord|bool $firstChild */ + $firstChild = reset($this->{$relationName}); + // If $firstChild is `false`, it might mean an empty array or non-ActiveRecord entries + if (!$firstChild instanceof ActiveRecord) { + continue; + } + + $relModelClass = get_class($firstChild); + + try { + if ($isSoftDelete) { + $relModelClass::updateAll($this->_rt_softdelete, ['and', $condition]); + } else { + $relModelClass::deleteAll(['and', $condition]); } + } catch (IntegrityException $exc) { + // Optionally include the actual DB exception message: + $message = Yii::t('app', "Cannot delete related data due to foreign key constraints. DB says: {error}", [ + 'error' => $exc->getMessage(), + ]); + $this->addError($relationName, $message); + $error = true; + break; } } + if ($error) { - $trans->rollback(); + $trans->rollBack(); return false; } + + // Finally handle the parent if ($isSoftDelete) { $this->attributes = array_merge($this->attributes, $this->_rt_softdelete); if ($this->save(false)) { $trans->commit(); return true; - } else { - $trans->rollBack(); - } - } else { - if ($this->delete()) { - $trans->commit(); - return true; - } else { - $trans->rollBack(); } + $trans->rollBack(); + return false; + } + + if ($this->delete()) { + $trans->commit(); + return true; } + $trans->rollBack(); + return false; } catch (Exception $exc) { $trans->rollBack(); throw $exc; @@ -425,157 +579,106 @@ public function deleteWithRelated($skippedRelations = []) } /** - * Restore soft deleted row including all related records - * @param array $skippedRelations + * Restore a soft-deleted row, including all related records. + * Requires $_rt_softrestore to be defined in the model. + * + * @param array $skippedRelations Relations to exclude from restore. * @return bool * @throws Exception */ - public function restoreWithRelated($skippedRelations = []) + public function restoreWithRelated(array $skippedRelations = []): bool { if (!isset($this->_rt_softrestore)) { return false; } - /* @var $this ActiveRecord */ + /** @var ActiveRecord $this */ $db = $this->getDb(); $trans = $db->beginTransaction(); + $error = false; + try { - $error = false; $relData = $this->getRelationData(); foreach ($relData as $data) { - $array = []; - if ($data['ismultiple'] && !in_array($data['name'], $skippedRelations)) { - $link = $data['link']; - if (count($this->{$data['name']})) { - foreach ($link as $key => $value) { - if (isset($this->$value)) { - $array[$key] = $this->$value; - } - } - $error = !$this->{$data['name']}[0]->updateAll($this->_rt_softrestore, ['and', $array]); - } + if (!$data['ismultiple'] || in_array($data['name'], $skippedRelations, true)) { + continue; + } + + $relationName = $data['name']; + if (empty($this->{$relationName}) || !is_array($this->{$relationName})) { + continue; + } + + $condition = $this->buildConditionFromLink($data['link']); + if (empty($condition)) { + continue; + } + + /** @var ActiveRecord|bool $firstChild */ + $firstChild = reset($this->{$relationName}); + if (!$firstChild instanceof ActiveRecord) { + // if it's not a valid AR instance, skip + continue; + } + + $relModelClass = get_class($firstChild); + + try { + $relModelClass::updateAll($this->_rt_softrestore, ['and', $condition]); + } catch (IntegrityException $exc) { + $message = Yii::t('app', "Cannot restore related data due to foreign key constraints. DB says: {error}", [ + 'error' => $exc->getMessage(), + ]); + $this->addError($relationName, $message); + $error = true; + break; } } + if ($error) { - $trans->rollback(); + $trans->rollBack(); return false; } + + // Restore the parent $this->attributes = array_merge($this->attributes, $this->_rt_softrestore); if ($this->save(false)) { $trans->commit(); return true; - } else { - $trans->rollBack(); } + + $trans->rollBack(); + return false; } catch (Exception $exc) { $trans->rollBack(); throw $exc; } } - public function getRelationData() + /** + * Deprecated: Return array structured for form POST data (the "multi-dimensional" approach). + * + * @return array + */ + public function getAttributesWithRelatedAsPost(): array { - $stack = []; - if (method_exists($this, 'relationNames')) { - foreach ($this->relationNames() as $name) { - /* @var $rel ActiveQuery */ - $rel = $this->getRelation($name); - $stack[$name]['name'] = $name; - $stack[$name]['method'] = 'get' . ucfirst($name); - $stack[$name]['ismultiple'] = $rel->multiple; - $stack[$name]['modelClass'] = $rel->modelClass; - $stack[$name]['link'] = $rel->link; - $stack[$name]['via'] = $rel->via; - $stack[$name]['where'] = []; - - if (!empty($rel->on)) { - $stack[$name]['where'] = $rel->on; - } - - if (!empty($rel->where)) { - $stack[$name]['where'] = $rel->where; - } - } - } else { - $ARMethods = get_class_methods('\yii\db\ActiveRecord'); - $modelMethods = get_class_methods('\yii\base\Model'); - $reflection = new \ReflectionClass($this); - /* @var $method \ReflectionMethod */ - foreach ($reflection->getMethods() as $method) { - if (in_array($method->name, $ARMethods) || in_array($method->name, $modelMethods)) { - continue; - } - if ($method->name === 'getRelationData') { - continue; - } - if ($method->name === 'getAttributesWithRelatedAsPost') { - continue; - } - if ($method->name === 'getAttributesWithRelated') { - continue; - } - if (strpos($method->name, 'get') !== 0) { - continue; - } - if($method->getNumberOfParameters() > 0) { - continue; - } - try { - $rel = call_user_func(array($this, $method->name)); - if ($rel instanceof ActiveQuery) { - $name = lcfirst(preg_replace('/^get/', '', $method->name)); - $stack[$name]['name'] = lcfirst(preg_replace('/^get/', '', $method->name)); - $stack[$name]['method'] = $method->name; - $stack[$name]['ismultiple'] = $rel->multiple; - $stack[$name]['modelClass'] = $rel->modelClass; - $stack[$name]['link'] = $rel->link; - $stack[$name]['via'] = $rel->via; - $stack[$name]['where'] = []; - - if (!empty($rel->on)) { - $stack[$name]['where'] = $rel->on; - } - - if (!empty($rel->where)) { - $stack[$name]['where'] = $rel->where; - } - } - } catch (\Exception $exc) { - //if method name can't be called, - } - } - } - return $stack; + $shortName = StringHelper::basename(static::class); + $return = [ + $shortName => $this->attributes, + ]; + return $this->getRelatedRecordsTree($return); } /** - * This function is deprecated! - * Return array like this - * Array - * ( - * [MainClass] => Array - * ( - * [attr1] => value1 - * [attr2] => value2 - * ) + * Builds a nested array representation of the current model's related records. * - * [RelatedClass] => Array - * ( - * [0] => Array - * ( - * [attr1] => value1 - * [attr2] => value2 - * ) - * ) - * ) + * @param array $return * @return array */ - public function getAttributesWithRelatedAsPost() + public function getRelatedRecordsTree(array $return): array { - $return = []; - $shortName = StringHelper::basename(get_class($this)); - $return[$shortName] = $this->attributes; foreach ($this->relatedRecords as $name => $records) { + /** @var ActiveQuery $AQ */ $AQ = $this->getRelation($name); if ($AQ->multiple) { foreach ($records as $index => $record) { @@ -584,72 +687,44 @@ public function getAttributesWithRelatedAsPost() } else { $return[$name] = $records->attributes; } - } return $return; } /** - * return array like this - * Array - * ( - * [attr1] => value1 - * [attr2] => value2 - * [relationName] => Array - * ( - * [0] => Array - * ( - * [attr1] => value1 - * [attr2] => value2 - * ) - * ) - * ) + * Return array of attributes including related records in a single tree. + * * @return array */ - public function getAttributesWithRelated() + public function getAttributesWithRelated(): array { - /* @var $this ActiveRecord */ + /** @var ActiveRecord $this */ $return = $this->attributes; - foreach ($this->relatedRecords as $name => $records) { - $AQ = $this->getRelation($name); - if ($AQ->multiple) { - foreach ($records as $index => $record) { - $return[$name][$index] = $record->attributes; - } - } else { - $return[$name] = $records->attributes; - } - } - return $return; + return $this->getRelatedRecordsTree($return); } /** - * TranslationTrait manages methods for all translations used in Krajee extensions - * - * @author Kartik Visweswaran - * @since 1.8.8 - * Yii i18n messages configuration for generating translations - * source : https://github.com/kartik-v/yii2-krajee-base/blob/master/TranslationTrait.php - * Edited by : Yohanes Candrajaya + * Initialize i18n configuration for message translation. * - * - * @return void + * @throws \Exception */ - public function initI18N() + public function initI18N(): void { - $reflector = new \ReflectionClass(get_class($this)); + $reflector = new ReflectionClass(static::class); $dir = dirname($reflector->getFileName()); - Yii::setAlias("@mtrelt", $dir); + Yii::setAlias('@mtrelt', $dir); + $config = [ - 'class' => 'yii\i18n\PhpMessageSource', - 'basePath' => "@mtrelt/messages", - 'forceTranslation' => true + 'class' => PhpMessageSource::class, + 'basePath' => '@mtrelt/messages', + 'forceTranslation' => true, ]; - $globalConfig = ArrayHelper::getValue(Yii::$app->i18n->translations, "mtrelt*", []); + $globalConfig = ArrayHelper::getValue(Yii::$app->i18n->translations, 'mtrelt*', []); + if (!empty($globalConfig)) { $config = array_merge($config, is_array($globalConfig) ? $globalConfig : (array)$globalConfig); } - Yii::$app->i18n->translations["mtrelt*"] = $config; + Yii::$app->i18n->translations['mtrelt*'] = $config; } } diff --git a/composer.json b/composer.json index 72f251a..5b27b80 100644 --- a/composer.json +++ b/composer.json @@ -1,31 +1,53 @@ { - "name": "deadmantfa/yii2-relation-trait", - "type": "yii2-extension", - "description": "Yii 2 Models load with relation, & transaction save with relation", - "keywords": ["Yii2","relation","load","save","transaction","loadwithrelation", "savewithrelation", "related", "saveall", "loadall"], - "homepage": "http://github.com/deadmantfa/yii2-relation-trait", - "license": "BSD-3-Clause", - "support": { - "issues": "https://github.com/deadmantfa/yii2-relation-trait/issues", - "source": "https://github.com/deadmantfa/yii2-relation-trait" + "name": "deadmantfa/yii2-relation-trait", + "type": "yii2-extension", + "description": "Yii 2 Models load with relation & transaction save with relation (plus optional soft-delete/restore).", + "keywords": [ + "Yii2", + "relation", + "load", + "save", + "transaction", + "loadwithrelation", + "savewithrelation", + "related", + "saveall", + "loadall" + ], + "homepage": "https://github.com/deadmantfa/yii2-relation-trait", + "license": "BSD-3-Clause", + "support": { + "issues": "https://github.com/deadmantfa/yii2-relation-trait/issues", + "source": "https://github.com/deadmantfa/yii2-relation-trait" + }, + "authors": [ + { + "name": "Yohanes Candrajaya", + "email": "moo.tensai@gmail.com" }, - "authors": [ - { - "name": "Yohanes Candrajaya", - "email": "moo.tensai@gmail.com" - }, - { - "name": "Wenceslaus Dsilva", - "email": "wenceslausdsilva@gmail.com" - } - ], - "require": { - "php": ">=5.4.0", - "yiisoft/yii2": "~2.0" - }, - "autoload": { - "psr-4": { - "deadmantfa\\relation\\": "" - } + { + "name": "Wenceslaus Dsilva", + "email": "wenceslausdsilva@gmail.com" + } + ], + "require": { + "php": ">=7.4.0", + "yiisoft/yii2": "^2.0.51" + }, + "autoload": { + "psr-4": { + "deadmantfa\\relation\\": "" + } + }, + "repositories": [ + { + "type": "composer", + "url": "https://asset-packagist.org" + } + ], + "config": { + "allow-plugins": { + "yiisoft/yii2-composer": true } + } } diff --git a/composer.lock b/composer.lock index 914003d..3c68d24 100644 --- a/composer.lock +++ b/composer.lock @@ -1,180 +1,98 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "hash": "575850dd6075d5ccbccf7b6b61fdfdfb", + "content-hash": "576311f88bfbaaea5ee0f914bd7b3c12", "packages": [ { - "name": "bower-asset/jquery", - "version": "2.1.4", + "name": "bower-asset/inputmask", + "version": "5.0.8", "source": { "type": "git", - "url": "https://github.com/jquery/jquery.git", - "reference": "7751e69b615c6eca6f783a81e292a55725af6b85" + "url": "git@github.com:RobinHerbots/Inputmask.git", + "reference": "e0f39e0c93569c6b494c3a57edef2c59313a6b64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jquery/jquery/zipball/7751e69b615c6eca6f783a81e292a55725af6b85", - "reference": "7751e69b615c6eca6f783a81e292a55725af6b85", - "shasum": "" - }, - "require-dev": { - "bower-asset/qunit": "1.14.0", - "bower-asset/requirejs": "2.1.10", - "bower-asset/sinon": "1.8.1", - "bower-asset/sizzle": "2.1.1-patch2" + "url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/e0f39e0c93569c6b494c3a57edef2c59313a6b64", + "reference": "e0f39e0c93569c6b494c3a57edef2c59313a6b64" }, - "type": "bower-asset-library", - "extra": { - "bower-asset-main": "dist/jquery.js", - "bower-asset-ignore": [ - "**/.*", - "build", - "dist/cdn", - "speed", - "test", - "*.md", - "AUTHORS.txt", - "Gruntfile.js", - "package.json" - ] + "require": { + "bower-asset/jquery": ">=1.7" }, + "type": "bower-asset", "license": [ - "MIT" - ], - "keywords": [ - "javascript", - "jquery", - "library" + "http://opensource.org/licenses/mit-license.php" ] }, { - "name": "bower-asset/jquery.inputmask", - "version": "3.1.63", + "name": "bower-asset/jquery", + "version": "3.6.4", "source": { "type": "git", - "url": "https://github.com/RobinHerbots/jquery.inputmask.git", - "reference": "c40c7287eadc31e341ebbf0c02352eb55b9cbc48" + "url": "git@github.com:jquery/jquery-dist.git", + "reference": "91ef2d8836342875f2519b5815197ea0f23613cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/RobinHerbots/jquery.inputmask/zipball/c40c7287eadc31e341ebbf0c02352eb55b9cbc48", - "reference": "c40c7287eadc31e341ebbf0c02352eb55b9cbc48", - "shasum": "" - }, - "require": { - "bower-asset/jquery": ">=1.7" - }, - "type": "bower-asset-library", - "extra": { - "bower-asset-main": [ - "./dist/inputmask/jquery.inputmask.js", - "./dist/inputmask/jquery.inputmask.extensions.js", - "./dist/inputmask/jquery.inputmask.date.extensions.js", - "./dist/inputmask/jquery.inputmask.numeric.extensions.js", - "./dist/inputmask/jquery.inputmask.phone.extensions.js", - "./dist/inputmask/jquery.inputmask.regex.extensions.js" - ], - "bower-asset-ignore": [ - "**/.*", - "qunit/", - "nuget/", - "tools/", - "js/", - "*.md", - "build.properties", - "build.xml", - "jquery.inputmask.jquery.json" - ] + "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/91ef2d8836342875f2519b5815197ea0f23613cf", + "reference": "91ef2d8836342875f2519b5815197ea0f23613cf" }, + "type": "bower-asset", "license": [ - "http://opensource.org/licenses/mit-license.php" - ], - "description": "jquery.inputmask is a jquery plugin which create an input mask.", - "keywords": [ - "form", - "input", - "inputmask", - "jquery", - "mask", - "plugins" + "MIT" ] }, { "name": "bower-asset/punycode", - "version": "v1.3.2", + "version": "v2.3.1", "source": { "type": "git", - "url": "https://github.com/bestiejs/punycode.js.git", - "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3" + "url": "https://github.com/mathiasbynens/punycode.js.git", + "reference": "9e1b2cda98d215d3a73fcbfe93c62e021f4ba768" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bestiejs/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3", - "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3", - "shasum": "" + "url": "https://api.github.com/repos/mathiasbynens/punycode.js/zipball/9e1b2cda98d215d3a73fcbfe93c62e021f4ba768", + "reference": "9e1b2cda98d215d3a73fcbfe93c62e021f4ba768" }, - "type": "bower-asset-library", - "extra": { - "bower-asset-main": "punycode.js", - "bower-asset-ignore": [ - "coverage", - "tests", - ".*", - "component.json", - "Gruntfile.js", - "node_modules", - "package.json" - ] - } + "type": "bower-asset" }, { "name": "bower-asset/yii2-pjax", - "version": "v2.0.4", + "version": "2.0.8", "source": { "type": "git", - "url": "https://github.com/yiisoft/jquery-pjax.git", - "reference": "3f20897307cca046fca5323b318475ae9dac0ca0" + "url": "git@github.com:yiisoft/jquery-pjax.git", + "reference": "a9298d57da63d14a950f1b94366a864bc62264fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/3f20897307cca046fca5323b318475ae9dac0ca0", - "reference": "3f20897307cca046fca5323b318475ae9dac0ca0", - "shasum": "" + "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/a9298d57da63d14a950f1b94366a864bc62264fb", + "reference": "a9298d57da63d14a950f1b94366a864bc62264fb" }, "require": { "bower-asset/jquery": ">=1.8" }, - "type": "bower-asset-library", - "extra": { - "bower-asset-main": "./jquery.pjax.js", - "bower-asset-ignore": [ - ".travis.yml", - "Gemfile", - "Gemfile.lock", - "vendor/", - "script/", - "test/" - ] - }, + "type": "bower-asset", "license": [ "MIT" ] }, { "name": "cebe/markdown", - "version": "1.1.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/cebe/markdown.git", - "reference": "54a2c49de31cc44e864ebf0500a35ef21d0010b2" + "reference": "9bac5e971dd391e2802dca5400bbeacbaea9eb86" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cebe/markdown/zipball/54a2c49de31cc44e864ebf0500a35ef21d0010b2", - "reference": "54a2c49de31cc44e864ebf0500a35ef21d0010b2", + "url": "https://api.github.com/repos/cebe/markdown/zipball/9bac5e971dd391e2802dca5400bbeacbaea9eb86", + "reference": "9bac5e971dd391e2802dca5400bbeacbaea9eb86", "shasum": "" }, "require": { @@ -192,7 +110,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -221,37 +139,54 @@ "markdown", "markdown-extra" ], - "time": "2015-03-06 05:28:07" + "support": { + "issues": "https://github.com/cebe/markdown/issues", + "source": "https://github.com/cebe/markdown" + }, + "time": "2018-03-26T11:24:36+00:00" }, { "name": "ezyang/htmlpurifier", - "version": "v4.6.0", + "version": "v4.18.0", "source": { "type": "git", "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "6f389f0f25b90d0b495308efcfa073981177f0fd" + "reference": "cb56001e54359df7ae76dc522d08845dc741621b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/6f389f0f25b90d0b495308efcfa073981177f0fd", - "reference": "6f389f0f25b90d0b495308efcfa073981177f0fd", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/cb56001e54359df7ae76dc522d08845dc741621b", + "reference": "cb56001e54359df7ae76dc522d08845dc741621b", "shasum": "" }, "require": { - "php": ">=5.2" + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" + }, + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-tidy": "Used for pretty-printing HTML" }, "type": "library", "autoload": { + "files": [ + "library/HTMLPurifier.composer.php" + ], "psr-0": { "HTMLPurifier": "library/" }, - "files": [ - "library/HTMLPurifier.composer.php" + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL" + "LGPL-2.1-or-later" ], "authors": [ { @@ -265,33 +200,89 @@ "keywords": [ "html" ], - "time": "2013-11-30 08:25:19" + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.18.0" + }, + "time": "2024-11-01T03:51:45+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" }, { "name": "yiisoft/yii2", - "version": "2.0.5", + "version": "2.0.51", "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "ea8c13b9f5cd437bd7bf73cad8a3457a155f3727" + "reference": "ea1f112f4dc9a9824e77b788019e2d53325d823c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/ea8c13b9f5cd437bd7bf73cad8a3457a155f3727", - "reference": "ea8c13b9f5cd437bd7bf73cad8a3457a155f3727", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/ea1f112f4dc9a9824e77b788019e2d53325d823c", + "reference": "ea1f112f4dc9a9824e77b788019e2d53325d823c", "shasum": "" }, "require": { - "bower-asset/jquery": "2.1.*@stable | 1.11.*@stable", - "bower-asset/jquery.inputmask": "3.1.*", - "bower-asset/punycode": "1.3.*", - "bower-asset/yii2-pjax": ">=2.0.1", - "cebe/markdown": "~1.0.0 | ~1.1.0", + "bower-asset/inputmask": "^5.0.8 ", + "bower-asset/jquery": "3.7.*@stable | 3.6.*@stable | 3.5.*@stable | 3.4.*@stable | 3.3.*@stable | 3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", + "bower-asset/punycode": "^2.2", + "bower-asset/yii2-pjax": "~2.0.1", + "cebe/markdown": "~1.0.0 | ~1.1.0 | ~1.2.0", + "ext-ctype": "*", "ext-mbstring": "*", - "ezyang/htmlpurifier": "4.6.*", + "ezyang/htmlpurifier": "^4.17", "lib-pcre": "*", - "php": ">=5.4.0", - "yiisoft/yii2-composer": "*" + "paragonie/random_compat": ">=1", + "php": ">=7.3.0", + "yiisoft/yii2-composer": "~2.0.4" }, "bin": [ "yii" @@ -315,13 +306,13 @@ { "name": "Qiang Xue", "email": "qiang.xue@gmail.com", - "homepage": "http://www.yiiframework.com/", + "homepage": "https://www.yiiframework.com/", "role": "Founder and project lead" }, { "name": "Alexander Makarov", "email": "sam@rmcreative.ru", - "homepage": "http://rmcreative.ru/", + "homepage": "https://rmcreative.ru/", "role": "Core framework development" }, { @@ -332,7 +323,7 @@ { "name": "Carsten Brandt", "email": "mail@cebe.cc", - "homepage": "http://cebe.cc/", + "homepage": "https://www.cebe.cc/", "role": "Core framework development" }, { @@ -345,32 +336,68 @@ "name": "Paul Klimov", "email": "klimov.paul@gmail.com", "role": "Core framework development" + }, + { + "name": "Dmitry Naumenko", + "email": "d.naumenko.a@gmail.com", + "role": "Core framework development" + }, + { + "name": "Boudewijn Vahrmeijer", + "email": "info@dynasource.eu", + "homepage": "http://dynasource.eu", + "role": "Core framework development" } ], "description": "Yii PHP Framework Version 2", - "homepage": "http://www.yiiframework.com/", + "homepage": "https://www.yiiframework.com/", "keywords": [ "framework", "yii2" ], - "time": "2015-07-11 02:37:59" + "support": { + "forum": "https://forum.yiiframework.com/", + "irc": "ircs://irc.libera.chat:6697/yii", + "issues": "https://github.com/yiisoft/yii2/issues?state=open", + "source": "https://github.com/yiisoft/yii2", + "wiki": "https://www.yiiframework.com/wiki" + }, + "funding": [ + { + "url": "https://github.com/yiisoft", + "type": "github" + }, + { + "url": "https://opencollective.com/yiisoft", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/yiisoft/yii2", + "type": "tidelift" + } + ], + "time": "2024-07-18T19:50:00+00:00" }, { "name": "yiisoft/yii2-composer", - "version": "2.0.3", + "version": "2.0.10", "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-composer.git", - "reference": "ca8d23707ae47d20b0454e4b135c156f6da6d7be" + "reference": "94bb3f66e779e2774f8776d6e1bdeab402940510" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/ca8d23707ae47d20b0454e4b135c156f6da6d7be", - "reference": "ca8d23707ae47d20b0454e4b135c156f6da6d7be", + "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/94bb3f66e779e2774f8776d6e1bdeab402940510", + "reference": "94bb3f66e779e2774f8776d6e1bdeab402940510", "shasum": "" }, "require": { - "composer-plugin-api": "1.0.0" + "composer-plugin-api": "^1.0 | ^2.0" + }, + "require-dev": { + "composer/composer": "^1.0 | ^2.0@dev", + "phpunit/phpunit": "<7" }, "type": "composer-plugin", "extra": { @@ -392,6 +419,10 @@ { "name": "Qiang Xue", "email": "qiang.xue@gmail.com" + }, + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc" } ], "description": "The composer plugin for Yii extension installer", @@ -400,17 +431,39 @@ "extension installer", "yii2" ], - "time": "2015-03-01 06:22:44" + "support": { + "forum": "http://www.yiiframework.com/forum/", + "irc": "irc://irc.freenode.net/yii", + "issues": "https://github.com/yiisoft/yii2-composer/issues", + "source": "https://github.com/yiisoft/yii2-composer", + "wiki": "http://www.yiiframework.com/wiki/" + }, + "funding": [ + { + "url": "https://github.com/yiisoft", + "type": "github" + }, + { + "url": "https://opencollective.com/yiisoft", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/yiisoft/yii2-composer", + "type": "tidelift" + } + ], + "time": "2020-06-24T00:04:01+00:00" } ], "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=5.4.0" + "php": ">=7.4.0" }, - "platform-dev": [] + "platform-dev": {}, + "plugin-api-version": "2.6.0" } From 906d58274976977e0a0c2405cedf786b03ce7774 Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Mon, 6 Jan 2025 14:50:34 +0530 Subject: [PATCH 17/22] =?UTF-8?q?=F0=9F=93=9D=20(README.md):=20update=20in?= =?UTF-8?q?stallation=20instructions=20to=20specify=20stable=20version=20o?= =?UTF-8?q?f=20yii2-relation-trait=20package?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 67b7214..8c78e19 100644 --- a/README.md +++ b/README.md @@ -26,13 +26,13 @@ The preferred way to install this extension is through [Composer](http://getcomp Either run ```bash -composer require deadmantfa/yii2-relation-trait:dev-master +composer require deadmantfa/yii2-relation-trait ``` or add ```php -"deadmantfa/yii2-relation-trait": "*" +"deadmantfa/yii2-relation-trait": "^2.0.0" ``` to the require section of your application's ```composer.json``` file. From 7f1146cfd1f7d9807434116fe73a6dba30dbf7d3 Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Mon, 13 Jan 2025 16:28:55 +0530 Subject: [PATCH 18/22] =?UTF-8?q?=F0=9F=93=9D=20(RelationTrait.php):=20rem?= =?UTF-8?q?ove=20return=20type=20annotations=20from=20docblocks=20to=20avo?= =?UTF-8?q?id=20redundancy=20with=20actual=20return=20type=20declarations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔧 (composer.json): add rector/rector as a development dependency to facilitate automated code refactoring 🔧 (rector.php): add configuration file for Rector to set up paths and code quality levels for automated refactoring --- RelationTrait.php | 123 ++++++++++++++++++++-------------------------- composer.json | 3 ++ composer.lock | 122 ++++++++++++++++++++++++++++++++++++++++++++- rector.php | 16 ++++++ 4 files changed, 192 insertions(+), 72 deletions(-) create mode 100644 rector.php diff --git a/RelationTrait.php b/RelationTrait.php index f3e1c32..af816eb 100644 --- a/RelationTrait.php +++ b/RelationTrait.php @@ -25,13 +25,12 @@ */ trait RelationTrait { - /** - * Load model attributes including related attributes from POST data. - * - * @param array $POST The POST data (likely Yii::$app->request->post()). - * @param array $skippedRelations Relations to exclude from load. - * @return bool - */ + /** + * Load model attributes including related attributes from POST data. + * + * @param array $POST The POST data (likely Yii::$app->request->post()). + * @param array $skippedRelations Relations to exclude from load. + */ public function loadAll(array $POST, array $skippedRelations = []): bool { if (!$this->load($POST)) { @@ -78,13 +77,11 @@ public function loadAll(array $POST, array $skippedRelations = []): bool return true; } - /** - * Retrieves information about all relations defined in this model by scanning: - * - 'relationNames()' method if defined in the model (must return an array of relation names as strings), or - * - reflection for 'getXYZ()' methods returning an ActiveQueryInterface. - * - * @return array - */ + /** + * Retrieves information about all relations defined in this model by scanning: + * - 'relationNames()' method if defined in the model (must return an array of relation names as strings), or + * - reflection for 'getXYZ()' methods returning an ActiveQueryInterface. + */ public function getRelationData(): array { $stack = []; @@ -158,14 +155,13 @@ public function getRelationData(): array return $stack; } - /** - * Load array data into a single relation (HasOne, HasMany, or ManyMany). - * - * @param bool $isHasMany Whether this relation is plural (HasMany / ManyMany). - * @param string $relName The relation name in the AR model. - * @param array $v The data array for that relation. - * @return bool - */ + /** + * Load array data into a single relation (HasOne, HasMany, or ManyMany). + * + * @param bool $isHasMany Whether this relation is plural (HasMany / ManyMany). + * @param string $relName The relation name in the AR model. + * @param array $v The data array for that relation. + */ private function loadToRelation(bool $isHasMany, string $relName, array $v): bool { /** @var ActiveRecord $this */ @@ -245,14 +241,13 @@ private function loadToRelation(bool $isHasMany, string $relName, array $v): boo return true; } - /** - * Save model and all related records in a transaction. - * Optionally uses soft-delete if configured. - * - * @param array $skippedRelations Relations to exclude from save. - * @return bool - * @throws Exception - */ + /** + * Save model and all related records in a transaction. + * Optionally uses soft-delete if configured. + * + * @param array $skippedRelations Relations to exclude from save. + * @throws Exception + */ public function saveAll(array $skippedRelations = []): bool { /** @var ActiveRecord $this */ @@ -470,14 +465,11 @@ private function safeDeleteOrSoftDelete( } } - /** - * A small helper for building a 'where' condition array from a relation link array. - * - * For example, if $link = ['foreign_key_id' => 'id'], we build `['foreign_key_id' => $this->id]`. - * - * @param array $link - * @return array - */ + /** + * A small helper for building a 'where' condition array from a relation link array. + * + * For example, if $link = ['foreign_key_id' => 'id'], we build `['foreign_key_id' => $this->id]`. + */ private function buildConditionFromLink(array $link): array { $condition = []; @@ -489,14 +481,13 @@ private function buildConditionFromLink(array $link): array return $condition; } - /** - * Delete this model and all related records. - * If soft-delete is configured, it will only mark them as deleted. - * - * @param array $skippedRelations Relations to exclude from deletion. - * @return bool - * @throws Exception|Throwable - */ + /** + * Delete this model and all related records. + * If soft-delete is configured, it will only mark them as deleted. + * + * @param array $skippedRelations Relations to exclude from deletion. + * @throws Exception|Throwable + */ public function deleteWithRelated(array $skippedRelations = []): bool { /** @var ActiveRecord $this */ @@ -578,14 +569,13 @@ public function deleteWithRelated(array $skippedRelations = []): bool } } - /** - * Restore a soft-deleted row, including all related records. - * Requires $_rt_softrestore to be defined in the model. - * - * @param array $skippedRelations Relations to exclude from restore. - * @return bool - * @throws Exception - */ + /** + * Restore a soft-deleted row, including all related records. + * Requires $_rt_softrestore to be defined in the model. + * + * @param array $skippedRelations Relations to exclude from restore. + * @throws Exception + */ public function restoreWithRelated(array $skippedRelations = []): bool { if (!isset($this->_rt_softrestore)) { @@ -655,11 +645,9 @@ public function restoreWithRelated(array $skippedRelations = []): bool } } - /** - * Deprecated: Return array structured for form POST data (the "multi-dimensional" approach). - * - * @return array - */ + /** + * Deprecated: Return array structured for form POST data (the "multi-dimensional" approach). + */ public function getAttributesWithRelatedAsPost(): array { $shortName = StringHelper::basename(static::class); @@ -669,12 +657,9 @@ public function getAttributesWithRelatedAsPost(): array return $this->getRelatedRecordsTree($return); } - /** - * Builds a nested array representation of the current model's related records. - * - * @param array $return - * @return array - */ + /** + * Builds a nested array representation of the current model's related records. + */ public function getRelatedRecordsTree(array $return): array { foreach ($this->relatedRecords as $name => $records) { @@ -691,11 +676,9 @@ public function getRelatedRecordsTree(array $return): array return $return; } - /** - * Return array of attributes including related records in a single tree. - * - * @return array - */ + /** + * Return array of attributes including related records in a single tree. + */ public function getAttributesWithRelated(): array { /** @var ActiveRecord $this */ diff --git a/composer.json b/composer.json index 5b27b80..24b7bc8 100644 --- a/composer.json +++ b/composer.json @@ -49,5 +49,8 @@ "allow-plugins": { "yiisoft/yii2-composer": true } + }, + "require-dev": { + "rector/rector": "^2.0" } } diff --git a/composer.lock b/composer.lock index 3c68d24..edbe4bf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "576311f88bfbaaea5ee0f914bd7b3c12", + "content-hash": "a30601a3188f08f07fdf2a1b95757267", "packages": [ { "name": "bower-asset/inputmask", @@ -455,7 +455,125 @@ "time": "2020-06-24T00:04:01+00:00" } ], - "packages-dev": [], + "packages-dev": [ + { + "name": "phpstan/phpstan", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", + "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-01-05T16:43:48+00:00" + }, + { + "name": "rector/rector", + "version": "2.0.6", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "fa0cb009dc3df084bf549032ae4080a0481a2036" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/fa0cb009dc3df084bf549032ae4080a0481a2036", + "reference": "fa0cb009dc3df084bf549032ae4080a0481a2036", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "phpstan/phpstan": "^2.1.1" + }, + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" + }, + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "keywords": [ + "automation", + "dev", + "migration", + "refactoring" + ], + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/2.0.6" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2025-01-06T10:38:36+00:00" + } + ], "aliases": [], "minimum-stability": "stable", "stability-flags": {}, diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..48f38af --- /dev/null +++ b/rector.php @@ -0,0 +1,16 @@ +withPaths([ + __DIR__ . '/messages', + ]) + ->withRootFiles() + // uncomment to reach your current PHP version + // ->withPhpSets() + ->withTypeCoverageLevel(20) + ->withDeadCodeLevel(20) + ->withCodeQualityLevel(20); From 8ac396a6eca728332a4828696e7e78c41badca8d Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Tue, 14 Jan 2025 14:54:37 +0530 Subject: [PATCH 19/22] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(RelationTrait.php):?= =?UTF-8?q?=20remove=20unnecessary=20else=20statements=20to=20simplify=20c?= =?UTF-8?q?ode=20structure=20and=20improve=20readability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔧 (rector.php): increase type coverage, dead code, and code quality levels to 40 to enhance code robustness and maintainability --- RelationTrait.php | 60 +++++++++++++++++++++-------------------------- rector.php | 6 ++--- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/RelationTrait.php b/RelationTrait.php index af816eb..19b82db 100644 --- a/RelationTrait.php +++ b/RelationTrait.php @@ -277,19 +277,17 @@ public function saveAll(array $skippedRelations = []): bool continue; } - if ($AQ->multiple) { - // ManyMany or HasMany - $firstRelModel = is_array($records) ? reset($records) : null; + if ($AQ->multiple) { + // ManyMany or HasMany + $firstRelModel = is_array($records) ? reset($records) : null; if (!$firstRelModel instanceof ActiveRecord) { continue; - } - $relPKAttr = $firstRelModel->primaryKey(); - $isManyMany = (count($relPKAttr) > 1); - - // Collect PK & FK for leftover deletion - $notDeletedPK = []; - $notDeletedFK = []; - + } + $relPKAttr = $firstRelModel->primaryKey(); + $isManyMany = (count($relPKAttr) > 1); + // Collect PK & FK for leftover deletion + $notDeletedPK = []; + $notDeletedFK = []; foreach ($records as $index => $relModel) { if (!$relModel instanceof ActiveRecord) { continue; @@ -312,9 +310,8 @@ public function saveAll(array $skippedRelations = []): bool } else { $notDeletedPK[] = $relModel->primaryKey; } - } - - // For existing parent, remove leftover children + } + // For existing parent, remove leftover children if (!$isNewRecord) { $relationLabel = Yii::t('app', Inflector::camel2words(StringHelper::basename($AQ->modelClass))); $relModel = $firstRelModel; @@ -348,9 +345,8 @@ public function saveAll(array $skippedRelations = []): bool ); } } - } - - // Now save each related record + } + // Now save each related record foreach ($records as $index => $relModel) { if (!$relModel->save() || !empty($relModel->errors)) { $relModelWords = Yii::t('app', Inflector::camel2words(StringHelper::basename($AQ->modelClass))); @@ -362,24 +358,22 @@ public function saveAll(array $skippedRelations = []): bool } $error = true; } - } - } else { - // HasOne - if (!is_array($records) && $records instanceof ActiveRecord) { - $relModel = $records; - foreach ($link as $key => $value) { - $relModel->{$key} = $this->{$value}; - } - if (!$relModel->save() || !empty($relModel->errors)) { - $recordsWords = Yii::t('app', Inflector::camel2words(StringHelper::basename($AQ->modelClass))); - foreach ($relModel->errors as $validation) { - foreach ($validation as $errorMsg) { - $this->addError($name, "$recordsWords : $errorMsg"); - } + } + } elseif (!is_array($records) && $records instanceof ActiveRecord) { + // HasOne + $relModel = $records; + foreach ($link as $key => $value) { + $relModel->{$key} = $this->{$value}; + } + if (!$relModel->save() || !empty($relModel->errors)) { + $recordsWords = Yii::t('app', Inflector::camel2words(StringHelper::basename($AQ->modelClass))); + foreach ($relModel->errors as $validation) { + foreach ($validation as $errorMsg) { + $this->addError($name, "$recordsWords : $errorMsg"); } - $error = true; } - } + $error = true; + } } } } diff --git a/rector.php b/rector.php index 48f38af..22f7fa0 100644 --- a/rector.php +++ b/rector.php @@ -11,6 +11,6 @@ ->withRootFiles() // uncomment to reach your current PHP version // ->withPhpSets() - ->withTypeCoverageLevel(20) - ->withDeadCodeLevel(20) - ->withCodeQualityLevel(20); + ->withTypeCoverageLevel(40) + ->withDeadCodeLevel(40) + ->withCodeQualityLevel(40); From 3121d0551e38211de351514e640b8188d1f097c7 Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Tue, 14 Jan 2025 14:59:02 +0530 Subject: [PATCH 20/22] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(RelationTrait.php):?= =?UTF-8?q?=20replace=20empty()=20checks=20with=20strict=20array=20compari?= =?UTF-8?q?son=20for=20better=20readability=20and=20performance=20?= =?UTF-8?q?=F0=9F=94=A7=20(rector.php):=20increase=20type=20coverage,=20de?= =?UTF-8?q?ad=20code,=20and=20code=20quality=20levels=20to=2070=20for=20st?= =?UTF-8?q?ricter=20code=20analysis=20and=20improvement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RelationTrait.php | 4 ++-- rector.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/RelationTrait.php b/RelationTrait.php index 19b82db..3d24c53 100644 --- a/RelationTrait.php +++ b/RelationTrait.php @@ -333,7 +333,7 @@ public function saveAll(array $skippedRelations = []): bool } else { // HasMany leftover cleanup $primaryColumn = $relPKAttr[0] ?? null; - if ($primaryColumn !== null && !empty($notDeletedPK)) { + if ($primaryColumn !== null && $notDeletedPK !== []) { $query = ['and', $notDeletedFK, ['not in', $primaryColumn, $notDeletedPK]]; $this->safeDeleteOrSoftDelete( $relModel, @@ -443,7 +443,7 @@ private function safeDeleteOrSoftDelete( ): void { try { - if (!empty($softDeleteData)) { + if ($softDeleteData !== []) { $modelClassOrInstance->updateAll($softDeleteData, $condition); } else { $modelClassOrInstance->deleteAll($condition); diff --git a/rector.php b/rector.php index 22f7fa0..9232dc0 100644 --- a/rector.php +++ b/rector.php @@ -11,6 +11,6 @@ ->withRootFiles() // uncomment to reach your current PHP version // ->withPhpSets() - ->withTypeCoverageLevel(40) - ->withDeadCodeLevel(40) - ->withCodeQualityLevel(40); + ->withTypeCoverageLevel(70) + ->withDeadCodeLevel(70) + ->withCodeQualityLevel(70); From 7b91286fe8179cfaa6eb0ad9a2cffd24df09ebec Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Tue, 14 Jan 2025 14:59:56 +0530 Subject: [PATCH 21/22] =?UTF-8?q?=F0=9F=94=A7=20(rector.php):=20increase?= =?UTF-8?q?=20type=20coverage,=20dead=20code,=20and=20code=20quality=20lev?= =?UTF-8?q?els=20to=2080?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rector.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rector.php b/rector.php index 9232dc0..febc08f 100644 --- a/rector.php +++ b/rector.php @@ -11,6 +11,6 @@ ->withRootFiles() // uncomment to reach your current PHP version // ->withPhpSets() - ->withTypeCoverageLevel(70) - ->withDeadCodeLevel(70) - ->withCodeQualityLevel(70); + ->withTypeCoverageLevel(80) + ->withDeadCodeLevel(80) + ->withCodeQualityLevel(80); From e6fd00d67c5e59a04d1032e5b14ec7c740b41c90 Mon Sep 17 00:00:00 2001 From: Wenceslaus Dsilva Date: Fri, 24 Jan 2025 19:34:01 +0530 Subject: [PATCH 22/22] =?UTF-8?q?=F0=9F=92=A1=20(RelationTrait.php):=20con?= =?UTF-8?q?vert=20line=20endings=20from=20LF=20to=20CRLF=20for=20consisten?= =?UTF-8?q?cy=20with=20other=20files=20in=20the=20project?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RelationTrait.php | 148 +++++++++++++++++++++++----------------------- 1 file changed, 75 insertions(+), 73 deletions(-) diff --git a/RelationTrait.php b/RelationTrait.php index 3d24c53..cf003fa 100644 --- a/RelationTrait.php +++ b/RelationTrait.php @@ -25,12 +25,12 @@ */ trait RelationTrait { - /** - * Load model attributes including related attributes from POST data. - * - * @param array $POST The POST data (likely Yii::$app->request->post()). - * @param array $skippedRelations Relations to exclude from load. - */ + /** + * Load model attributes including related attributes from POST data. + * + * @param array $POST The POST data (likely Yii::$app->request->post()). + * @param array $skippedRelations Relations to exclude from load. + */ public function loadAll(array $POST, array $skippedRelations = []): bool { if (!$this->load($POST)) { @@ -77,11 +77,11 @@ public function loadAll(array $POST, array $skippedRelations = []): bool return true; } - /** - * Retrieves information about all relations defined in this model by scanning: - * - 'relationNames()' method if defined in the model (must return an array of relation names as strings), or - * - reflection for 'getXYZ()' methods returning an ActiveQueryInterface. - */ + /** + * Retrieves information about all relations defined in this model by scanning: + * - 'relationNames()' method if defined in the model (must return an array of relation names as strings), or + * - reflection for 'getXYZ()' methods returning an ActiveQueryInterface. + */ public function getRelationData(): array { $stack = []; @@ -155,13 +155,13 @@ public function getRelationData(): array return $stack; } - /** - * Load array data into a single relation (HasOne, HasMany, or ManyMany). - * - * @param bool $isHasMany Whether this relation is plural (HasMany / ManyMany). - * @param string $relName The relation name in the AR model. - * @param array $v The data array for that relation. - */ + /** + * Load array data into a single relation (HasOne, HasMany, or ManyMany). + * + * @param bool $isHasMany Whether this relation is plural (HasMany / ManyMany). + * @param string $relName The relation name in the AR model. + * @param array $v The data array for that relation. + */ private function loadToRelation(bool $isHasMany, string $relName, array $v): bool { /** @var ActiveRecord $this */ @@ -241,13 +241,13 @@ private function loadToRelation(bool $isHasMany, string $relName, array $v): boo return true; } - /** - * Save model and all related records in a transaction. - * Optionally uses soft-delete if configured. - * - * @param array $skippedRelations Relations to exclude from save. - * @throws Exception - */ + /** + * Save model and all related records in a transaction. + * Optionally uses soft-delete if configured. + * + * @param array $skippedRelations Relations to exclude from save. + * @throws Exception + */ public function saveAll(array $skippedRelations = []): bool { /** @var ActiveRecord $this */ @@ -277,17 +277,17 @@ public function saveAll(array $skippedRelations = []): bool continue; } - if ($AQ->multiple) { - // ManyMany or HasMany - $firstRelModel = is_array($records) ? reset($records) : null; + if ($AQ->multiple) { + // ManyMany or HasMany + $firstRelModel = is_array($records) ? reset($records) : null; if (!$firstRelModel instanceof ActiveRecord) { continue; - } - $relPKAttr = $firstRelModel->primaryKey(); - $isManyMany = (count($relPKAttr) > 1); - // Collect PK & FK for leftover deletion - $notDeletedPK = []; - $notDeletedFK = []; + } + $relPKAttr = $firstRelModel->primaryKey(); + $isManyMany = (count($relPKAttr) > 1); + // Collect PK & FK for leftover deletion + $notDeletedPK = []; + $notDeletedFK = []; foreach ($records as $index => $relModel) { if (!$relModel instanceof ActiveRecord) { continue; @@ -310,8 +310,8 @@ public function saveAll(array $skippedRelations = []): bool } else { $notDeletedPK[] = $relModel->primaryKey; } - } - // For existing parent, remove leftover children + } + // For existing parent, remove leftover children if (!$isNewRecord) { $relationLabel = Yii::t('app', Inflector::camel2words(StringHelper::basename($AQ->modelClass))); $relModel = $firstRelModel; @@ -345,8 +345,8 @@ public function saveAll(array $skippedRelations = []): bool ); } } - } - // Now save each related record + } + // Now save each related record foreach ($records as $index => $relModel) { if (!$relModel->save() || !empty($relModel->errors)) { $relModelWords = Yii::t('app', Inflector::camel2words(StringHelper::basename($AQ->modelClass))); @@ -358,13 +358,13 @@ public function saveAll(array $skippedRelations = []): bool } $error = true; } - } - } elseif (!is_array($records) && $records instanceof ActiveRecord) { - // HasOne - $relModel = $records; + } + } elseif (!is_array($records) && $records instanceof ActiveRecord) { + // HasOne + $relModel = $records; foreach ($link as $key => $value) { $relModel->{$key} = $this->{$value}; - } + } if (!$relModel->save() || !empty($relModel->errors)) { $recordsWords = Yii::t('app', Inflector::camel2words(StringHelper::basename($AQ->modelClass))); foreach ($relModel->errors as $validation) { @@ -373,7 +373,7 @@ public function saveAll(array $skippedRelations = []): bool } } $error = true; - } + } } } } @@ -459,11 +459,11 @@ private function safeDeleteOrSoftDelete( } } - /** - * A small helper for building a 'where' condition array from a relation link array. - * - * For example, if $link = ['foreign_key_id' => 'id'], we build `['foreign_key_id' => $this->id]`. - */ + /** + * A small helper for building a 'where' condition array from a relation link array. + * + * For example, if $link = ['foreign_key_id' => 'id'], we build `['foreign_key_id' => $this->id]`. + */ private function buildConditionFromLink(array $link): array { $condition = []; @@ -475,13 +475,13 @@ private function buildConditionFromLink(array $link): array return $condition; } - /** - * Delete this model and all related records. - * If soft-delete is configured, it will only mark them as deleted. - * - * @param array $skippedRelations Relations to exclude from deletion. - * @throws Exception|Throwable - */ + /** + * Delete this model and all related records. + * If soft-delete is configured, it will only mark them as deleted. + * + * @param array $skippedRelations Relations to exclude from deletion. + * @throws Exception|Throwable + */ public function deleteWithRelated(array $skippedRelations = []): bool { /** @var ActiveRecord $this */ @@ -510,7 +510,9 @@ public function deleteWithRelated(array $skippedRelations = []): bool } /** @var ActiveRecord|bool $firstChild */ - $firstChild = reset($this->{$relationName}); + $relationRecords = $this->{$relationName}; + $firstChild = reset($relationRecords); + // If $firstChild is `false`, it might mean an empty array or non-ActiveRecord entries if (!$firstChild instanceof ActiveRecord) { continue; @@ -563,13 +565,13 @@ public function deleteWithRelated(array $skippedRelations = []): bool } } - /** - * Restore a soft-deleted row, including all related records. - * Requires $_rt_softrestore to be defined in the model. - * - * @param array $skippedRelations Relations to exclude from restore. - * @throws Exception - */ + /** + * Restore a soft-deleted row, including all related records. + * Requires $_rt_softrestore to be defined in the model. + * + * @param array $skippedRelations Relations to exclude from restore. + * @throws Exception + */ public function restoreWithRelated(array $skippedRelations = []): bool { if (!isset($this->_rt_softrestore)) { @@ -639,9 +641,9 @@ public function restoreWithRelated(array $skippedRelations = []): bool } } - /** - * Deprecated: Return array structured for form POST data (the "multi-dimensional" approach). - */ + /** + * Deprecated: Return array structured for form POST data (the "multi-dimensional" approach). + */ public function getAttributesWithRelatedAsPost(): array { $shortName = StringHelper::basename(static::class); @@ -651,9 +653,9 @@ public function getAttributesWithRelatedAsPost(): array return $this->getRelatedRecordsTree($return); } - /** - * Builds a nested array representation of the current model's related records. - */ + /** + * Builds a nested array representation of the current model's related records. + */ public function getRelatedRecordsTree(array $return): array { foreach ($this->relatedRecords as $name => $records) { @@ -670,9 +672,9 @@ public function getRelatedRecordsTree(array $return): array return $return; } - /** - * Return array of attributes including related records in a single tree. - */ + /** + * Return array of attributes including related records in a single tree. + */ public function getAttributesWithRelated(): array { /** @var ActiveRecord $this */