From e3f1fc36828d9fb30666c1d5e413924325d8dfdf Mon Sep 17 00:00:00 2001 From: MDW Date: Sun, 31 Mar 2024 16:06:04 +0200 Subject: [PATCH 1/3] Cope with magic and real properties aliases in _filterObjectProperties --- htdocs/api/class/api.class.php | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/htdocs/api/class/api.class.php b/htdocs/api/class/api.class.php index 63d504b14cb..2187b988c47 100644 --- a/htdocs/api/class/api.class.php +++ b/htdocs/api/class/api.class.php @@ -2,6 +2,7 @@ /* Copyright (C) 2015 Jean-François Ferry * Copyright (C) 2016 Laurent Destailleur * Copyright (C) 2020 Frédéric France + * Copyright (C) 2024 MDW * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -107,16 +108,41 @@ class DolibarrApi */ protected function _filterObjectProperties($object, $properties) { + // phpcs:enable // If properties is empty, we return all properties if (empty($properties)) { return $object; } - // Else we filter properties + + // Copy of exploded array for efficiency + $arr_properties = explode(',', $properties); + $magic_properties = array(); + $real_properties = get_object_vars($object); + + // Unsetting real properties may unset magic properties. + // We keep a copy of the requested magic properties + foreach ($arr_properties as $key) { + if (!array_key_exists($key, $real_properties)) { + // Not a real property, + // check if $key is a magic property (we want to keep '$obj->$key') + if (property_exists($object, $key) && isset($object->$key)) { + $magic_properties[$key] = $object->$key; + } + } + } + + // Filter real properties (may indirectly unset magic properties) foreach (get_object_vars($object) as $key => $value) { - if (!in_array($key, explode(',', $properties))) { + if (!in_array($key, $arr_properties)) { unset($object->$key); } } + + // Restore the magic properties + foreach ($magic_properties as $key => $value) { + $object->$key = $value; + } + return $object; } From 31584659e0ae6fa53011ec1cc14aa273d48364b1 Mon Sep 17 00:00:00 2001 From: MDW Date: Sun, 31 Mar 2024 16:12:55 +0200 Subject: [PATCH 2/3] Refactor objCompare # Refactor objCompare: - Move to CommonClassTest; - Report class name for mismatched property; - Cope with dolDeprecated property aliases (test may exclude deprecated name, also exclude new name). --- test/phpunit/CommonClassTest.class.php | 63 ++++++++++++++++++++++++++ test/phpunit/FactureRecTest.php | 35 +------------- test/phpunit/FactureTest.php | 38 ++-------------- test/phpunit/InventoryTest.php | 35 +------------- test/phpunit/UserTest.php | 35 +------------- 5 files changed, 69 insertions(+), 137 deletions(-) diff --git a/test/phpunit/CommonClassTest.class.php b/test/phpunit/CommonClassTest.class.php index 0db9b170871..ee5db8c46d4 100644 --- a/test/phpunit/CommonClassTest.class.php +++ b/test/phpunit/CommonClassTest.class.php @@ -235,6 +235,69 @@ abstract class CommonClassTest extends TestCase } } + + /** + * Call method, even if protected. + * + * @param object $obj Object on which to call method + * @param string $name Method to call + * @param array $args Arguments to provide in method call + * @return mixed Return value + */ + public static function callMethod($obj, $name, array $args = []) + { + $class = new \ReflectionClass($obj); + $method = $class->getMethod($name); + // If PHP is older then 8.1.0 + if (PHP_VERSION_ID < 80100) { + $method->setAccessible(true); + } + return $method->invokeArgs($obj, $args); + } + + + /** + * Compare all public properties values of 2 objects + * + * @param Object $oA Object operand 1 + * @param Object $oB Object operand 2 + * @param boolean $ignoretype False will not report diff if type of value differs + * @param array $fieldstoignorearray Array of fields to ignore in diff + * @return array Array with differences + */ + public function objCompare($oA, $oB, $ignoretype = true, $fieldstoignorearray = array('id')) + { + $retAr = array(); + + if (get_class($oA) !== get_class($oB)) { + $retAr[] = "Supplied objects are not of same class."; + } else { + $oVarsA = get_object_vars($oA); + $oVarsB = get_object_vars($oB); + $aKeys = array_keys($oVarsA); + if (method_exists($oA, 'deprecatedProperties')) { + // Update exclusions + foreach (self::callMethod($oA, 'deprecatedProperties') as $deprecated => $new) { + if (in_array($deprecated, $fieldstoignorearray)) { + $fieldstoignorearray[] = $new; + } + } + } + foreach ($aKeys as $sKey) { + if (in_array($sKey, $fieldstoignorearray)) { + continue; + } + if (! $ignoretype && ($oVarsA[$sKey] !== $oVarsB[$sKey])) { + $retAr[] = get_class($oA).'::'.$sKey.' : '.(is_object($oVarsA[$sKey]) ? get_class($oVarsA[$sKey]) : json_encode($oVarsA[$sKey])).' <> '.(is_object($oVarsB[$sKey]) ? get_class($oVarsB[$sKey]) : json_encode($oVarsB[$sKey])); + } + if ($ignoretype && ($oVarsA[$sKey] != $oVarsB[$sKey])) { + $retAr[] = get_class($oA).'::'.$sKey.' : '.(is_object($oVarsA[$sKey]) ? get_class($oVarsA[$sKey]) : json_encode($oVarsA[$sKey])).' <> '.(is_object($oVarsB[$sKey]) ? get_class($oVarsB[$sKey]) : json_encode($oVarsB[$sKey])); + } + } + } + return $retAr; + } + /** * Map deprecated module names to new module names */ diff --git a/test/phpunit/FactureRecTest.php b/test/phpunit/FactureRecTest.php index 2644c4fe084..801a6656131 100644 --- a/test/phpunit/FactureRecTest.php +++ b/test/phpunit/FactureRecTest.php @@ -1,6 +1,7 @@ * Copyright (C) 2023 Alexandre Janniaux + * Copyright (C) 2024 MDW * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -117,38 +118,4 @@ class FactureRecTest extends CommonClassTest $localobject->note_private = 'New note'; //$localobject->note='New note after update'; } - - /** - * Compare all public properties values of 2 objects - * - * @param Object $oA Object operand 1 - * @param Object $oB Object operand 2 - * @param boolean $ignoretype False will not report diff if type of value differs - * @param array $fieldstoignorearray Array of fields to ignore in diff - * @return array Array with differences - */ - public function objCompare($oA, $oB, $ignoretype = true, $fieldstoignorearray = array('id')) - { - $retAr = array(); - - if (get_class($oA) !== get_class($oB)) { - $retAr[] = "Supplied objects are not of same class."; - } else { - $oVarsA = get_object_vars($oA); - $oVarsB = get_object_vars($oB); - $aKeys = array_keys($oVarsA); - foreach ($aKeys as $sKey) { - if (in_array($sKey, $fieldstoignorearray)) { - continue; - } - if (! $ignoretype && ($oVarsA[$sKey] !== $oVarsB[$sKey])) { - $retAr[] = $sKey.' : '.(is_object($oVarsA[$sKey]) ? get_class($oVarsA[$sKey]) : $oVarsA[$sKey]).' <> '.(is_object($oVarsB[$sKey]) ? get_class($oVarsB[$sKey]) : $oVarsB[$sKey]); - } - if ($ignoretype && ($oVarsA[$sKey] != $oVarsB[$sKey])) { - $retAr[] = $sKey.' : '.(is_object($oVarsA[$sKey]) ? get_class($oVarsA[$sKey]) : $oVarsA[$sKey]).' <> '.(is_object($oVarsB[$sKey]) ? get_class($oVarsB[$sKey]) : $oVarsB[$sKey]); - } - } - } - return $retAr; - } } diff --git a/test/phpunit/FactureTest.php b/test/phpunit/FactureTest.php index e2f9fa5f68c..2fc0bb6d67d 100644 --- a/test/phpunit/FactureTest.php +++ b/test/phpunit/FactureTest.php @@ -2,6 +2,7 @@ /* Copyright (C) 2010 Laurent Destailleur * Copyright (C) 2018 Frédéric France * Copyright (C) 2023 Alexandre Janniaux + * Copyright (C) 2024 MDW * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -155,7 +156,7 @@ class FactureTest extends CommonClassTest $this->assertLessThan($result, 0); - // Test everything are still same than specimen + // Test everything is still the same as specimen $newlocalobject = new Facture($db); $newlocalobject->initAsSpecimen(); $this->changeProperties($newlocalobject); @@ -168,6 +169,7 @@ class FactureTest extends CommonClassTest $localobject, $newlocalobject, true, + // Not comparing: array( 'newref','oldref','id','lines','client','thirdparty','brouillon','user_creation_id','date_creation','date_validation','datem','date_modification', 'ref','statut','status','paye','specimen','ref','actiontypecode','actionmsg2','actionmsg','mode_reglement','cond_reglement', @@ -281,38 +283,4 @@ class FactureTest extends CommonClassTest $localobject->note_private = 'New note'; //$localobject->note='New note after update'; } - - /** - * Compare all public properties values of 2 objects - * - * @param Object $oA Object operand 1 - * @param Object $oB Object operand 2 - * @param boolean $ignoretype False will not report diff if type of value differs - * @param array $fieldstoignorearray Array of fields to ignore in diff - * @return array Array with differences - */ - public function objCompare($oA, $oB, $ignoretype = true, $fieldstoignorearray = array('id')) - { - $retAr = array(); - - if (get_class($oA) !== get_class($oB)) { - $retAr[] = "Supplied objects are not of same class."; - } else { - $oVarsA = get_object_vars($oA); - $oVarsB = get_object_vars($oB); - $aKeys = array_keys($oVarsA); - foreach ($aKeys as $sKey) { - if (in_array($sKey, $fieldstoignorearray)) { - continue; - } - if (! $ignoretype && ($oVarsA[$sKey] !== $oVarsB[$sKey])) { - $retAr[] = $sKey.' : '.(is_object($oVarsA[$sKey]) ? get_class($oVarsA[$sKey]) : $oVarsA[$sKey]).' <> '.(is_object($oVarsB[$sKey]) ? get_class($oVarsB[$sKey]) : $oVarsB[$sKey]); - } - if ($ignoretype && ($oVarsA[$sKey] != $oVarsB[$sKey])) { - $retAr[] = $sKey.' : '.(is_object($oVarsA[$sKey]) ? get_class($oVarsA[$sKey]) : $oVarsA[$sKey]).' <> '.(is_object($oVarsB[$sKey]) ? get_class($oVarsB[$sKey]) : $oVarsB[$sKey]); - } - } - } - return $retAr; - } } diff --git a/test/phpunit/InventoryTest.php b/test/phpunit/InventoryTest.php index 2d9808e12ce..7761608d1a6 100644 --- a/test/phpunit/InventoryTest.php +++ b/test/phpunit/InventoryTest.php @@ -2,6 +2,7 @@ /* Copyright (C) 2010 Laurent Destailleur * Copyright (C) 2018 Frédéric France * Copyright (C) 2023 Alexandre Janniaux + * Copyright (C) 2024 MDW * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -267,38 +268,4 @@ class InventoryTest extends CommonClassTest return $result; } - - /** - * Compare all public properties values of 2 objects - * - * @param Object $oA Object operand 1 - * @param Object $oB Object operand 2 - * @param boolean $ignoretype False will not report diff if type of value differs - * @param array $fieldstoignorearray Array of fields to ignore in diff - * @return array Array with differences - */ - public function objCompare($oA, $oB, $ignoretype = true, $fieldstoignorearray = array('id')) - { - $retAr = array(); - - if (get_class($oA) !== get_class($oB)) { - $retAr[] = "Supplied objects are not of same class."; - } else { - $oVarsA = get_object_vars($oA); - $oVarsB = get_object_vars($oB); - $aKeys = array_keys($oVarsA); - foreach ($aKeys as $sKey) { - if (in_array($sKey, $fieldstoignorearray)) { - continue; - } - if (! $ignoretype && ($oVarsA[$sKey] !== $oVarsB[$sKey])) { - $retAr[] = $sKey.' : '.(is_object($oVarsA[$sKey]) ? get_class($oVarsA[$sKey]) : $oVarsA[$sKey]).' <> '.(is_object($oVarsB[$sKey]) ? get_class($oVarsB[$sKey]) : $oVarsB[$sKey]); - } - if ($ignoretype && ($oVarsA[$sKey] != $oVarsB[$sKey])) { - $retAr[] = $sKey.' : '.(is_object($oVarsA[$sKey]) ? get_class($oVarsA[$sKey]) : $oVarsA[$sKey]).' <> '.(is_object($oVarsB[$sKey]) ? get_class($oVarsB[$sKey]) : $oVarsB[$sKey]); - } - } - } - return $retAr; - } } diff --git a/test/phpunit/UserTest.php b/test/phpunit/UserTest.php index 877d258d60b..d5328982f3a 100644 --- a/test/phpunit/UserTest.php +++ b/test/phpunit/UserTest.php @@ -1,6 +1,7 @@ * Copyright (C) 2023 Alexandre Janniaux + * Copyright (C) 2024 MDW * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -403,38 +404,4 @@ class UserTest extends CommonClassTest { $localobject->note_private = 'New note after update'; } - - /** - * Compare all public properties values of 2 objects - * - * @param Object $oA Object operand 1 - * @param Object $oB Object operand 2 - * @param boolean $ignoretype False will not report diff if type of value differs - * @param array $fieldstoignorearray Array of fields to ignore in diff - * @return array Array with differences - */ - public function objCompare($oA, $oB, $ignoretype = true, $fieldstoignorearray = array('id')) - { - $retAr = array(); - - if (get_class($oA) !== get_class($oB)) { - $retAr[] = "Supplied objects are not of same class."; - } else { - $oVarsA = get_object_vars($oA); - $oVarsB = get_object_vars($oB); - $aKeys = array_keys($oVarsA); - foreach ($aKeys as $sKey) { - if (in_array($sKey, $fieldstoignorearray)) { - continue; - } - if (! $ignoretype && ($oVarsA[$sKey] !== $oVarsB[$sKey])) { - $retAr[] = $sKey.' : '.(is_object($oVarsA[$sKey]) ? get_class($oVarsA[$sKey]) : $oVarsA[$sKey]).' <> '.(is_object($oVarsB[$sKey]) ? get_class($oVarsB[$sKey]) : $oVarsB[$sKey]); - } - if ($ignoretype && ($oVarsA[$sKey] != $oVarsB[$sKey])) { - $retAr[] = $sKey.' : '.(is_object($oVarsA[$sKey]) ? get_class($oVarsA[$sKey]) : $oVarsA[$sKey]).' <> '.(is_object($oVarsB[$sKey]) ? get_class($oVarsB[$sKey]) : $oVarsB[$sKey]); - } - } - } - return $retAr; - } } From 146b73526933fb3ec0f6d47cbe7b38fdb628b5fd Mon Sep 17 00:00:00 2001 From: MDW Date: Sun, 31 Mar 2024 16:22:53 +0200 Subject: [PATCH 3/3] New: dol_get_object_properties to get real and magic props # New: dol_get_object_properties to get real and magic props This method will get an array with real and magic properties from an object (if $properties is provided). --- htdocs/core/lib/functions.lib.php | 35 +++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 3d38aef2dfd..1c5a6bde9b2 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -1367,6 +1367,41 @@ function dol_buildpath($path, $type = 0, $returnemptyifnotfound = 0) return $res; } +/** + * Get properties for an object - including magic properties when requested + * + * Only returns properties that exist + * + * @param object $obj Object to get properties from + * @param string[] $properties Optional list of properties to get. + * When empty, only gets public properties. + * @return array Hash for retrieved values (key=name) + */ +function dol_get_object_properties($obj, $properties = []) +{ + // Get real properties using get_object_vars() if $properties is empty + if (empty($properties)) { + return get_object_vars($obj); + } + + $existingProperties = []; + $realProperties = get_object_vars($obj); + + // Get the real or magic property values + foreach ($properties as $property) { + if (array_key_exists($property, $realProperties)) { + // Real property, add the value + $existingProperties[$property] = $obj->{$property}; + } elseif (property_exists($obj, $property)) { + // Magic property + $existingProperties[$property] = $obj->{$property}; + } + } + + return $existingProperties; +} + + /** * Create a clone of instance of object (new instance with same value for each properties) * With native = 0: Property that are reference are different memory area in the new object (full isolation clone). This means $this->db of new object may not be valid.