From 62762374e08c100f3d586feca18514e1c19c7199 Mon Sep 17 00:00:00 2001 From: babaev-an Date: Sun, 13 Jul 2025 12:23:08 +0300 Subject: [PATCH] 20250713 1.1.1 --- .gitignore | 3 + composer.json | 2 +- composer.lock | 30 +-- sources/classes/ActionState.php | 4 +- sources/classes/ObjectArray.php | 6 +- sources/enums/ObjectArraySerializeMethod.php | 40 ++++ ...essageModel.php => ActionStateMessage.php} | 6 +- .../models/ObjectArraySerializeOptions.php | 52 ++++++ .../ActionState/ActionStateAddTrait.php | 16 +- .../ActionState/ActionStateHasTrait.php | 8 +- .../ActionState/ActionStateStaticTrait.php | 8 +- .../ObjectArraySerializeExTrait.php | 176 ++++++++++++++++++ tests/classes/ActionStateTest.php | 6 +- tests/classes/ObjectArrayTest.php | 70 +++++++ 14 files changed, 386 insertions(+), 41 deletions(-) create mode 100644 sources/enums/ObjectArraySerializeMethod.php rename sources/models/{ActionStateMessageModel.php => ActionStateMessage.php} (94%) create mode 100644 sources/models/ObjectArraySerializeOptions.php create mode 100644 sources/traits/ObjectArray/ObjectArraySerializeExTrait.php diff --git a/.gitignore b/.gitignore index 0bb7d32..62e7b7a 100644 --- a/.gitignore +++ b/.gitignore @@ -80,3 +80,6 @@ fabric.properties .idea/ vendor/ /tests/extensions/class.txt +/tests/classesserialized1.txt +/tests/classesserialized2.txt +/tests/classesserialized3.txt diff --git a/composer.json b/composer.json index 38967bf..49584ef 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "ext-mbstring": "*" }, "require-dev": { - "phpunit/phpunit": "^12.2.1" + "phpunit/phpunit": "^12.2.7" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 8ebe28c..bdfa3f2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,21 +4,21 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f5bf0ec4042cb12fb3a702cad65f099d", + "content-hash": "45c3c2e33a7aa403660825318375a599", "packages": [], "packages-dev": [ { "name": "myclabs/deep-copy", - "version": "1.13.1", + "version": "1.13.3", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36", + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36", "shasum": "" }, "require": { @@ -57,7 +57,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3" }, "funding": [ { @@ -65,7 +65,7 @@ "type": "tidelift" } ], - "time": "2025-04-29T12:36:36+00:00" + "time": "2025-07-05T12:25:42+00:00" }, { "name": "nikic/php-parser", @@ -579,16 +579,16 @@ }, { "name": "phpunit/phpunit", - "version": "12.2.5", + "version": "12.2.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "b71849b29f7a8d7574e4401873cb8b539896613f" + "reference": "8b1348b254e5959acaf1539c6bd790515fb49414" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b71849b29f7a8d7574e4401873cb8b539896613f", - "reference": "b71849b29f7a8d7574e4401873cb8b539896613f", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8b1348b254e5959acaf1539c6bd790515fb49414", + "reference": "8b1348b254e5959acaf1539c6bd790515fb49414", "shasum": "" }, "require": { @@ -598,7 +598,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.1", + "myclabs/deep-copy": "^1.13.3", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.3", @@ -656,7 +656,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/12.2.5" + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.2.7" }, "funding": [ { @@ -680,7 +680,7 @@ "type": "tidelift" } ], - "time": "2025-06-27T04:37:55+00:00" + "time": "2025-07-11T04:11:13+00:00" }, { "name": "sebastian/cli-parser", @@ -1633,4 +1633,4 @@ }, "platform-dev": {}, "plugin-api-version": "2.6.0" -} +} \ No newline at end of file diff --git a/sources/classes/ActionState.php b/sources/classes/ActionState.php index c907ad3..b3b0e99 100644 --- a/sources/classes/ActionState.php +++ b/sources/classes/ActionState.php @@ -8,7 +8,7 @@ namespace goodboyalex\php_components_pack\classes; use goodboyalex\php_components_pack\interfaces\ISerializable; -use goodboyalex\php_components_pack\models\ActionStateMessageModel; +use goodboyalex\php_components_pack\models\ActionStateMessage; use goodboyalex\php_components_pack\traits\ActionState\ActionStateAddTrait; use goodboyalex\php_components_pack\traits\ActionState\ActionStateGetTrait; use goodboyalex\php_components_pack\traits\ActionState\ActionStateHasTrait; @@ -103,7 +103,7 @@ final class ActionState implements ISerializable // Для каждого сообщения foreach ($list as $messageSerialized) { // - создаю новое сообщение - $message = new ActionStateMessageModel(); + $message = new ActionStateMessage(); // - десериализую его $message->UnSerialize($messageSerialized); // - добавляю в список diff --git a/sources/classes/ObjectArray.php b/sources/classes/ObjectArray.php index 38a4545..8c17f21 100644 --- a/sources/classes/ObjectArray.php +++ b/sources/classes/ObjectArray.php @@ -9,6 +9,7 @@ use goodboyalex\php_components_pack\traits\ArrayBasicTrait; use goodboyalex\php_components_pack\traits\ObjectArray\ObjectArrayConstantsTrait; use goodboyalex\php_components_pack\traits\ObjectArray\ObjectArrayLINQTrait; use goodboyalex\php_components_pack\traits\ObjectArray\ObjectArraySearchAndSortTrait; +use goodboyalex\php_components_pack\traits\ObjectArray\ObjectArraySerializeExTrait; use goodboyalex\php_components_pack\traits\ObjectArray\ObjectArraySpecialTrait; use IteratorAggregate; @@ -17,7 +18,7 @@ use IteratorAggregate; * * @author Александр Бабаев * @package php_components_pack - * @version 1.0.5 + * @version 1.0.6 * @since 1.0 */ final class ObjectArray implements ArrayAccess, IteratorAggregate, Countable, ISerializable @@ -42,6 +43,9 @@ final class ObjectArray implements ArrayAccess, IteratorAggregate, Countable, IS // Специальные методы use ObjectArraySpecialTrait; + // Расширенные методы для сериализации + use ObjectArraySerializeExTrait; + /** * Конструктор класса. * diff --git a/sources/enums/ObjectArraySerializeMethod.php b/sources/enums/ObjectArraySerializeMethod.php new file mode 100644 index 0000000..7184a6e --- /dev/null +++ b/sources/enums/ObjectArraySerializeMethod.php @@ -0,0 +1,40 @@ +SerializeMethod = $serializeMethod; + $this->OnClassTo = $onClassTo; + $this->OnClassFrom = $onClassFrom; + } +} \ No newline at end of file diff --git a/sources/traits/ActionState/ActionStateAddTrait.php b/sources/traits/ActionState/ActionStateAddTrait.php index ffe4b65..0990a4d 100644 --- a/sources/traits/ActionState/ActionStateAddTrait.php +++ b/sources/traits/ActionState/ActionStateAddTrait.php @@ -5,7 +5,7 @@ namespace goodboyalex\php_components_pack\traits\ActionState; use goodboyalex\php_components_pack\classes\ActionState; use goodboyalex\php_components_pack\classes\ObjectArray; use goodboyalex\php_components_pack\enums\MessageType; -use goodboyalex\php_components_pack\models\ActionStateMessageModel; +use goodboyalex\php_components_pack\models\ActionStateMessage; /** * Часть кода класса ActionState, отвечающая за методы добавления сообщений. @@ -30,7 +30,7 @@ trait ActionStateAddTrait // Если нужно очистить список сообщений if ($clearAllBefore) // - то очищаю список сообщений - $this->Clear(fn (ActionStateMessageModel $message) => true); + $this->Clear(fn (ActionStateMessage $message) => true); // Добавляю сообщения из другого состояния $this->AddRange($state->GetMessages(ActionState::GET_STRING_ALL())); @@ -61,17 +61,17 @@ trait ActionStateAddTrait */ public function AddCritical (string $message): void { - $this->Add(new ActionStateMessageModel(MessageType::Error, true, $message)); + $this->Add(new ActionStateMessage(MessageType::Error, true, $message)); } /** * Добавление сообщения. * - * @param ActionStateMessageModel $message Сообщение + * @param ActionStateMessage $message Сообщение * * @return void */ - public function Add (ActionStateMessageModel $message): void + public function Add (ActionStateMessage $message): void { $this->Messages->Add($message); } @@ -85,7 +85,7 @@ trait ActionStateAddTrait */ public function AddError (string $message): void { - $this->Add(new ActionStateMessageModel(MessageType::Error, false, $message)); + $this->Add(new ActionStateMessage(MessageType::Error, false, $message)); } /** @@ -97,7 +97,7 @@ trait ActionStateAddTrait */ public function AddWarning (string $message): void { - $this->Add(new ActionStateMessageModel(MessageType::Warning, false, $message)); + $this->Add(new ActionStateMessage(MessageType::Warning, false, $message)); } /** @@ -109,6 +109,6 @@ trait ActionStateAddTrait */ public function AddInfo (string $message): void { - $this->Add(new ActionStateMessageModel(MessageType::Info, false, $message)); + $this->Add(new ActionStateMessage(MessageType::Info, false, $message)); } } \ No newline at end of file diff --git a/sources/traits/ActionState/ActionStateHasTrait.php b/sources/traits/ActionState/ActionStateHasTrait.php index 09b3310..910cd4f 100644 --- a/sources/traits/ActionState/ActionStateHasTrait.php +++ b/sources/traits/ActionState/ActionStateHasTrait.php @@ -3,7 +3,7 @@ namespace goodboyalex\php_components_pack\traits\ActionState; use goodboyalex\php_components_pack\enums\MessageType; -use goodboyalex\php_components_pack\models\ActionStateMessageModel; +use goodboyalex\php_components_pack\models\ActionStateMessage; /** * Часть кода класса ActionState, отвечающая за методы проверки на наличие сообщений. @@ -22,7 +22,7 @@ trait ActionStateHasTrait */ public function HasInfos (): bool { - return $this->Messages->IsExist(fn (ActionStateMessageModel $message) + return $this->Messages->IsExist(fn (ActionStateMessage $message) => $message->MessageType == MessageType::Info); } @@ -48,7 +48,7 @@ trait ActionStateHasTrait */ public function HasErrors (bool $onlyCritical = false): bool { - return $this->Messages->IsExist(fn (ActionStateMessageModel $message): bool + return $this->Messages->IsExist(fn (ActionStateMessage $message): bool => $onlyCritical ? $message->MessageType == MessageType::Error && $message->IsCritical : $message->MessageType == MessageType::Error); @@ -61,7 +61,7 @@ trait ActionStateHasTrait */ public function HasWarnings (): bool { - return $this->Messages->IsExist(fn (ActionStateMessageModel $message) + return $this->Messages->IsExist(fn (ActionStateMessage $message) => $message->MessageType == MessageType::Warning); } diff --git a/sources/traits/ActionState/ActionStateStaticTrait.php b/sources/traits/ActionState/ActionStateStaticTrait.php index 2f12712..d11519e 100644 --- a/sources/traits/ActionState/ActionStateStaticTrait.php +++ b/sources/traits/ActionState/ActionStateStaticTrait.php @@ -4,7 +4,7 @@ namespace goodboyalex\php_components_pack\traits\ActionState; use Closure; use goodboyalex\php_components_pack\enums\MessageType; -use goodboyalex\php_components_pack\models\ActionStateMessageModel; +use goodboyalex\php_components_pack\models\ActionStateMessage; /** * Часть кода класса ActionState, отвечающая за статичные методы и константы. @@ -23,7 +23,7 @@ trait ActionStateStaticTrait */ public static function GET_STRING_ERROR_ONLY (): Closure { - return fn (ActionStateMessageModel $message) + return fn (ActionStateMessage $message) => $message->MessageType === MessageType::Error; } @@ -34,7 +34,7 @@ trait ActionStateStaticTrait */ public static function GET_STRING_ERROR_AND_WARNING (): Closure { - return fn (ActionStateMessageModel $message) + return fn (ActionStateMessage $message) => $message->MessageType === MessageType::Error || $message->MessageType === MessageType::Warning; } @@ -46,6 +46,6 @@ trait ActionStateStaticTrait */ public static function GET_STRING_ALL (): Closure { - return fn (ActionStateMessageModel $message) => true; + return fn (ActionStateMessage $message) => true; } } \ No newline at end of file diff --git a/sources/traits/ObjectArray/ObjectArraySerializeExTrait.php b/sources/traits/ObjectArray/ObjectArraySerializeExTrait.php new file mode 100644 index 0000000..6854596 --- /dev/null +++ b/sources/traits/ObjectArray/ObjectArraySerializeExTrait.php @@ -0,0 +1,176 @@ +SerializeMethod) { + // - стандартный метод UnSerialize + ObjectArraySerializeMethod::Serialize => self::UnSerializeBySerialize($serialized), + // - метод JsonEncode + ObjectArraySerializeMethod::JsonEncode => self::UnSerializeByJsonEncode($serialized), + // - метод JsonEncodeWithToArray + ObjectArraySerializeMethod::JsonEncodeWithToArray => self::UnSerializeByJsonEncodeWithToArray($serialized, + $options->OnClassFrom) + }; + } + + /** + * Десериализует массив объектов с помощью стандартного метода UnSerialize. + * + * @param string $serialized Сериализованный массив. + * + * @return ObjectArray Массив объектов. + */ + private static function UnSerializeBySerialize (string $serialized): ObjectArray + { + // Создаем новый объект + $result = new ObjectArray(); + + // Десериализуем массив объектов с помощью стандартного метода + $result->UnSerialize($serialized); + + // Возвращаем результат + return $result; + } + + /** + * Десериализует массив объектов с помощью метода JsonEncode. + * + * @param string $serialized Сериализованный массив. + * + * @return ObjectArray Массив объектов. + */ + private static function UnSerializeByJsonEncode (string $serialized): ObjectArray + { + // Десериализуем массив + $container = json_decode($serialized, false, flags: JSON_UNESCAPED_UNICODE); + + // Создаем новый объект + return new ObjectArray($container); + } + + /** + * Десериализует массив объектов с помощью метода JsonEncodeWithToArray. + * + * @param string $serialized Сериализованный массив. + * @param callable|null $onClass Функция обратного вызова для десериализации объекта методом JsonEncodeWithToArray. + * Она передается в качестве параметра в функцию FromArray и служит для правильного преобразования массива в + * тип. + * + * @return ObjectArray Массив объектов. + */ + private static function UnSerializeByJsonEncodeWithToArray (string $serialized, ?callable $onClass): ObjectArray + { + // Десериализуем массив + $container = json_decode($serialized, true, flags: JSON_UNESCAPED_UNICODE); + + // Создаем новый объект, в который будем помещать объекты + $result = new ObjectArray(); + + // Переберем все объекты в контейнере + foreach ($container as $item) + try { + // - пробуем преобразовать массив в объект, если это возможно + $result[] = TypeExtension::FromArray($item, $onClass); + } + catch (TypeException) { + // - если не получилось, то просто добавляем объект в массив + $result[] = $item; + } + + // Возвращаем результат + return $result; + } + + /** + * Сериализует массив объектов. + * + * @param ObjectArraySerializeOptions|null $options Настройки сериализации. По умолчанию используется настройка по + * умолчанию. + * + * @return string Сериализованный массив. + */ + public function SerializeEx (?ObjectArraySerializeOptions $options = null): string + { + // По умолчанию используем настройки по умолчанию + $options ??= new ObjectArraySerializeOptions(); + + // Сериализуем массив + return match ($options->SerializeMethod) { + // - стандартный метод Serialize + ObjectArraySerializeMethod::Serialize => $this->Serialize(), + // - метод JsonEncode + ObjectArraySerializeMethod::JsonEncode => $this->SerializeByJsonEncode(), + // - метод JsonEncodeWithToArray + ObjectArraySerializeMethod::JsonEncodeWithToArray => $this->SerializeByJsonEncodeWithToArray($options->OnClassTo) + }; + } + + /** + * Сериализует массив методом JsonEncode. + * + * @return string Сериализованный массив. + */ + private function SerializeByJsonEncode (): string + { + return json_encode($this->Container, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + } + + /** + * Сериализует массив методом JsonEncodeWithToArray. + * + * @param callable|null $onClass Функция обратного вызова для сериализации объекта методом JsonEncodeWithToArray. + * Она передается в качестве параметра в функцию ToArray и служит для правильного преобразования типа в массив. + * + * @return string Сериализованный массив. + */ + private function SerializeByJsonEncodeWithToArray (?callable $onClass): string + { + // Создадим массив, в который будем помещать объекты + $container = []; + + // Переберем все объекты в контейнере + foreach ($this->Container as $item) + try { + // - пробуем преобразовать объект в массив, если это возможно + $container[] = TypeExtension::ToArray($item, $onClass); + } + catch (TypeException) { + // - если не получилось, то просто добавляем объект в массив + $container[] = $item; + } + + // Возвращаем сериализованный массив + return json_encode($container, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + } +} \ No newline at end of file diff --git a/tests/classes/ActionStateTest.php b/tests/classes/ActionStateTest.php index b436b95..496171f 100644 --- a/tests/classes/ActionStateTest.php +++ b/tests/classes/ActionStateTest.php @@ -4,7 +4,7 @@ namespace goodboyalex\php_components_pack\tests\classes; use goodboyalex\php_components_pack\classes\ActionState; use goodboyalex\php_components_pack\enums\MessageType; -use goodboyalex\php_components_pack\models\ActionStateMessageModel; +use goodboyalex\php_components_pack\models\ActionStateMessage; use PHPUnit\Framework\TestCase; class ActionStateTest extends TestCase @@ -29,7 +29,7 @@ class ActionStateTest extends TestCase $this->assertEquals(2, $messages->Count()); $this->assertEquals("Non critical error", - $actionState->GetStringMessages(fn (ActionStateMessageModel $message) + $actionState->GetStringMessages(fn (ActionStateMessage $message) => $message->MessageType == MessageType::Error && !$message->IsCritical)); @@ -51,4 +51,4 @@ class ActionStateTest extends TestCase require_once __DIR__ . '/../../sources/traits/ActionState/ActionStateStaticTrait.php'; require_once __DIR__ . '/../../sources/enums/MessageType.php'; } -} +} \ No newline at end of file diff --git a/tests/classes/ObjectArrayTest.php b/tests/classes/ObjectArrayTest.php index 4f8af0e..9b6ecb4 100644 --- a/tests/classes/ObjectArrayTest.php +++ b/tests/classes/ObjectArrayTest.php @@ -3,8 +3,14 @@ namespace goodboyalex\php_components_pack\tests\classes; use goodboyalex\php_components_pack\classes\ObjectArray; +use goodboyalex\php_components_pack\enums\ObjectArraySerializeMethod; +use goodboyalex\php_components_pack\models\ObjectArraySerializeOptions; use goodboyalex\php_components_pack\tests\data\A; +use goodboyalex\php_components_pack\tests\data\B; +use goodboyalex\php_components_pack\tests\data\C; +use goodboyalex\php_components_pack\tests\data\D; use PHPUnit\Framework\TestCase; +use stdClass; class ObjectArrayTest extends TestCase { @@ -38,6 +44,8 @@ class ObjectArrayTest extends TestCase { require_once __DIR__ . '/../data/A.php'; require_once __DIR__ . '/../data/B.php'; + require_once __DIR__ . '/../data/C.php'; + require_once __DIR__ . '/../data/D.php'; require_once __DIR__ . '/../../sources/interfaces/ISerializable.php'; require_once __DIR__ . '/../../sources/traits/ArrayBasicTrait.php'; require_once __DIR__ . '/../../sources/traits/ObjectArray/ObjectArrayConstantsTrait.php'; @@ -480,4 +488,66 @@ class ObjectArrayTest extends TestCase $this->assertEquals(12, $a_Array->Count()); } + + public function testSerializeEx () + { + $this->PrepareForTest(); + + // Создаём тестовые классы + $class1 = new D ('test_string1', 12345, true, new A("test_string_A1", 6789, false), + new B("test_string_B1", 9876, "false"), new C("test_string_C1", 54321, true)); + $class2 = new D ('test_string2', 123456, false, new A("test_string_A1", 678910, true), + new B("test_string_B2", 98765, "true"), new C("test_string_C2", 543210, false)); + $class = new D ('test_string3', 123450, true, new A("test_string_A2", 67890, false), + new B("test_string_B3", 90876, "false"), new C("test_string_C3", 543201, true)); + + // Создаём массив объектов + $objectArray = new ObjectArray([$class1, $class2, $class]); + + // Сериализуем + $serialized1 = + $objectArray->SerializeEx(new ObjectArraySerializeOptions(ObjectArraySerializeMethod::Serialize)); + $serialized2 = + $objectArray->SerializeEx(new ObjectArraySerializeOptions(ObjectArraySerializeMethod::JsonEncode)); + $serialized3 = + $objectArray->SerializeEx(new ObjectArraySerializeOptions(ObjectArraySerializeMethod::JsonEncodeWithToArray, + D::CLOSURE_TO_ARRAY())); + + // Сохраняем в файл + file_put_contents(__DIR__ . 'serialized1.txt', $serialized1); + file_put_contents(__DIR__ . 'serialized2.txt', $serialized2); + file_put_contents(__DIR__ . 'serialized3.txt', $serialized3); + + // Проверяем, что всё хорошо + $this->assertNotEmpty($serialized1); + $this->assertNotEmpty($serialized2); + $this->assertNotEmpty($serialized3); + } + + public function testUnSerializeEx () + { + $this->PrepareForTest(); + + // Загружаем данные + $serialized1 = file_get_contents(__DIR__ . 'serialized1.txt'); + $serialized2 = file_get_contents(__DIR__ . 'serialized2.txt'); + $serialized3 = file_get_contents(__DIR__ . 'serialized3.txt'); + + // Десериализуем + $objectArray1 = ObjectArray::UnSerializeEx($serialized1, + new ObjectArraySerializeOptions(ObjectArraySerializeMethod::Serialize)); + $objectArray2 = ObjectArray::UnSerializeEx($serialized2, + new ObjectArraySerializeOptions(ObjectArraySerializeMethod::JsonEncode)); + $objectArray3 = ObjectArray::UnSerializeEx($serialized3, + new ObjectArraySerializeOptions(ObjectArraySerializeMethod::JsonEncodeWithToArray, + onClassFrom: D::CLOSURE_FROM_ARRAY())); + + // Проверяем, что всё хорошо + $this->assertEquals('test_string_A1', + $objectArray1->GetRow(fn (D $value) => $value->stringD == 'test_string1')->a->a); + $this->assertEquals('test_string_B2', + $objectArray2->GetRow(fn (stdClass $value) => $value->stringD == 'test_string2')->b->a); + $this->assertEquals('test_string_C3', + $objectArray3->GetRow(fn (D $value) => $value->stringD == 'test_string3')->c->stringC); + } } \ No newline at end of file