From 5f6c2be6ae80989b8ea217f0c956921286ea5f7f Mon Sep 17 00:00:00 2001 From: Lukas Scharmer Date: Mon, 25 Oct 2021 16:39:05 +0200 Subject: [PATCH 1/2] Create new array shuffled flow based on the Refinery. Refactor old shuffle to use the new way --- Modules/Test/classes/class.ilObjTestGUI.php | 3 +- .../classes/class.ilTestPlayerAbstractGUI.php | 18 ++-- Modules/Test/test/ilTestBaseTestCase.php | 9 ++ .../ilTestPlayerDynamicQuestionSetGUITest.php | 1 + Modules/Test/test/ilTestPlayerFactoryTest.php | 1 + .../ilTestPlayerFixedQuestionSetGUITest.php | 1 + .../ilTestPlayerRandomQuestionSetGUITest.php | 1 + .../classes/class.assClozeGap.php | 12 ++- .../classes/class.assClozeTest.php | 9 +- .../classes/class.assClozeTestGUI.php | 15 ++- .../classes/class.assKprimChoiceGUI.php | 2 +- .../classes/class.assMatchingQuestion.php | 20 ++-- .../classes/class.assMatchingQuestionGUI.php | 24 ++--- .../classes/class.assMultipleChoiceGUI.php | 2 +- .../classes/class.assOrderingHorizontal.php | 2 +- .../classes/class.assOrderingQuestion.php | 6 +- .../classes/class.assQuestion.php | 12 ++- .../classes/class.assSingleChoiceGUI.php | 2 +- .../classes/class.ilAssQuestionPreviewGUI.php | 22 +++-- .../classes/class.ilObjQuestionPoolGUI.php | 3 +- .../export/qti12/class.assClozeTestExport.php | 19 +++- .../feedback/class.ilAssClozeTestFeedback.php | 27 ++++-- .../ilAssLacCompositeValidator.php | 9 +- .../TestQuestionPool/test/assBaseTestCase.php | 6 ++ .../TestQuestionPool/test/assClozeGapTest.php | 93 +++++++++++-------- .../test/assClozeSelectGapTest.php | 30 ++++-- .../classes/class.ilArrayElementShuffler.php | 64 ------------- .../class.ilBaseRandomElementProvider.php | 32 ------- ...ss.ilDeterministicArrayElementProvider.php | 22 ----- ...interface.ilRandomArrayElementProvider.php | 19 ---- .../test/RandomizationStrategiesTest.php | 71 -------------- src/Refinery/Effect/Effect.php | 19 ++++ src/Refinery/Effect/IdentityEffect.php | 18 ++++ .../Transformation/LiftTransformation.php | 60 ++++++++++++ src/Refinery/Factory.php | 6 ++ src/Refinery/Random/Effect/ShuffleEffect.php | 29 ++++++ src/Refinery/Random/Group.php | 34 +++++++ src/Refinery/Random/Seed/GivenSeed.php | 26 ++++++ src/Refinery/Random/Seed/RandomSeed.php | 22 +++++ src/Refinery/Random/Seed/Seed.php | 12 +++ .../Transformation/ShuffleTransformation.php | 41 ++++++++ .../Transformation/LiftTransformationTest.php | 63 +++++++++++++ tests/Refinery/FactoryTest.php | 8 +- .../Refinery/Random/Effect/ShuffleEffect.php | 21 +++++ tests/Refinery/Random/GroupTest.php | 40 ++++++++ .../ShuffleTransformationTest.php | 37 ++++++++ 46 files changed, 663 insertions(+), 330 deletions(-) delete mode 100644 Services/Randomization/classes/class.ilArrayElementShuffler.php delete mode 100644 Services/Randomization/classes/class.ilBaseRandomElementProvider.php delete mode 100644 Services/Randomization/classes/class.ilDeterministicArrayElementProvider.php delete mode 100644 Services/Randomization/interfaces/interface.ilRandomArrayElementProvider.php delete mode 100644 Services/Randomization/test/RandomizationStrategiesTest.php create mode 100644 src/Refinery/Effect/Effect.php create mode 100644 src/Refinery/Effect/IdentityEffect.php create mode 100644 src/Refinery/Effect/Transformation/LiftTransformation.php create mode 100644 src/Refinery/Random/Effect/ShuffleEffect.php create mode 100644 src/Refinery/Random/Group.php create mode 100644 src/Refinery/Random/Seed/GivenSeed.php create mode 100644 src/Refinery/Random/Seed/RandomSeed.php create mode 100644 src/Refinery/Random/Seed/Seed.php create mode 100644 src/Refinery/Random/Transformation/ShuffleTransformation.php create mode 100644 tests/Refinery/Effect/Transformation/LiftTransformationTest.php create mode 100644 tests/Refinery/Random/Effect/ShuffleEffect.php create mode 100644 tests/Refinery/Random/GroupTest.php create mode 100644 tests/Refinery/Random/Transformation/ShuffleTransformationTest.php diff --git a/Modules/Test/classes/class.ilObjTestGUI.php b/Modules/Test/classes/class.ilObjTestGUI.php index 5d63a28e3f4f..da18c358b3e5 100755 --- a/Modules/Test/classes/class.ilObjTestGUI.php +++ b/Modules/Test/classes/class.ilObjTestGUI.php @@ -156,6 +156,7 @@ public function executeCommand() $tree = $DIC['tree']; $ilias = $DIC['ilias']; $ilUser = $DIC['ilUser']; + $randomGroup = $DIC->refinery()->random(); $cmd = $this->ctrl->getCmd("infoScreen"); @@ -659,7 +660,7 @@ public function executeCommand() $this->ctrl->saveParameter($this, "q_id"); require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionPreviewGUI.php'; - $gui = new ilAssQuestionPreviewGUI($this->ctrl, $this->tabs_gui, $this->tpl, $this->lng, $ilDB, $ilUser); + $gui = new ilAssQuestionPreviewGUI($this->ctrl, $this->tabs_gui, $this->tpl, $this->lng, $ilDB, $ilUser, $randomGroup); $gui->initQuestion($this->fetchAuthoringQuestionIdParameter(), $this->object->getId()); $gui->initPreviewSettings($this->object->getRefId()); diff --git a/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php b/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php index 3aa628102c43..59f38c1960eb 100755 --- a/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php +++ b/Modules/Test/classes/class.ilTestPlayerAbstractGUI.php @@ -1,6 +1,11 @@ processLocker = null; $this->testSession = null; $this->assSettings = null; + $this->randomGroup = $DIC->refinery()->random(); } protected function checkReadAccess() @@ -2534,17 +2542,13 @@ protected function initTestQuestionConfig(assQuestion $questionOBJ) /** * @param $questionId - * @return ilArrayElementShuffler + * @return Transformation */ protected function buildQuestionAnswerShuffler($questionId) { - require_once 'Services/Randomization/classes/class.ilArrayElementShuffler.php'; - $shuffler = new ilArrayElementShuffler(); - $fixedSeed = $this->buildFixedShufflerSeed($questionId); - $shuffler->setSeed($fixedSeed); - - return $shuffler; + + return $this->randomGroup->shuffleArray(new GivenSeed($fixedSeed)); } /** diff --git a/Modules/Test/test/ilTestBaseTestCase.php b/Modules/Test/test/ilTestBaseTestCase.php index b6a7b976bf0b..5acb4a6e4fff 100644 --- a/Modules/Test/test/ilTestBaseTestCase.php +++ b/Modules/Test/test/ilTestBaseTestCase.php @@ -9,6 +9,8 @@ use ILIAS\Filesystem\Filesystems; use ILIAS\HTTP\Services; use ILIAS\UI\Implementation\Factory; +use ILIAS\Refinery\Factory as RefineryFactory; +use ILIAS\Refinery\Random\Group as RandomGroup; /** * Class ilTestBaseClass @@ -216,4 +218,11 @@ protected function addGlobal_uiRenderer() : void { $this->setGlobalVariable("ui.renderer", $this->createMock(ILIAS\UI\Implementation\DefaultRenderer::class)); } + + protected function addGlobal_refinery() : void + { + $refineryMock = $this->getMockBuilder(RefineryFactory::class)->disableOriginalConstructor()->getMock(); + $refineryMock->expects(self::any())->method('random')->willReturn($this->getMockBuilder(RandomGroup::class)->getMock()); + $this->setGlobalVariable("refinery", $refineryMock); + } } diff --git a/Modules/Test/test/ilTestPlayerDynamicQuestionSetGUITest.php b/Modules/Test/test/ilTestPlayerDynamicQuestionSetGUITest.php index c7df7803149d..3799eac0ecbe 100644 --- a/Modules/Test/test/ilTestPlayerDynamicQuestionSetGUITest.php +++ b/Modules/Test/test/ilTestPlayerDynamicQuestionSetGUITest.php @@ -25,6 +25,7 @@ protected function setUp() : void $this->addGlobal_ilObjDataCache(); $this->addGlobal_rbacsystem(); $this->addGlobal_ilUser(); + $this->addGlobal_refinery(); $_GET["ref_id"] = 2; diff --git a/Modules/Test/test/ilTestPlayerFactoryTest.php b/Modules/Test/test/ilTestPlayerFactoryTest.php index 36bdf681f2da..a1ff8d6ae6a9 100644 --- a/Modules/Test/test/ilTestPlayerFactoryTest.php +++ b/Modules/Test/test/ilTestPlayerFactoryTest.php @@ -40,6 +40,7 @@ public function testGetPlayerGUI() : void $this->addGlobal_ilObjDataCache(); $_GET["ref_id"] = 2; $this->addGlobal_rbacsystem(); + $this->addGlobal_refinery(); $objTest = new ilObjTest(); diff --git a/Modules/Test/test/ilTestPlayerFixedQuestionSetGUITest.php b/Modules/Test/test/ilTestPlayerFixedQuestionSetGUITest.php index 7112fe76b3d5..6a9c5ff64f66 100644 --- a/Modules/Test/test/ilTestPlayerFixedQuestionSetGUITest.php +++ b/Modules/Test/test/ilTestPlayerFixedQuestionSetGUITest.php @@ -27,6 +27,7 @@ protected function setUp() : void $this->addGlobal_ilObjDataCache(); $this->addGlobal_rbacsystem(); $this->addGlobal_ilUser(); + $this->addGlobal_refinery(); $this->testObj = new ilTestPlayerFixedQuestionSetGUI( $this->createMock(ilObjTest::class) diff --git a/Modules/Test/test/ilTestPlayerRandomQuestionSetGUITest.php b/Modules/Test/test/ilTestPlayerRandomQuestionSetGUITest.php index cb5694e430bd..d9d6559067a8 100644 --- a/Modules/Test/test/ilTestPlayerRandomQuestionSetGUITest.php +++ b/Modules/Test/test/ilTestPlayerRandomQuestionSetGUITest.php @@ -25,6 +25,7 @@ protected function setUp() : void $this->addGlobal_ilObjDataCache(); $this->addGlobal_rbacsystem(); $this->addGlobal_ilUser(); + $this->addGlobal_refinery(); $_GET["ref_id"] = "0"; diff --git a/Modules/TestQuestionPool/classes/class.assClozeGap.php b/Modules/TestQuestionPool/classes/class.assClozeGap.php index 5c2f410e09a8..63b61f54f4b2 100755 --- a/Modules/TestQuestionPool/classes/class.assClozeGap.php +++ b/Modules/TestQuestionPool/classes/class.assClozeGap.php @@ -1,6 +1,8 @@ getShuffle()) { - return $shuffler->shuffle($this->items); + return $shuffler->transform($this->items)->value(); } return $this->items; @@ -334,11 +336,11 @@ public function getBestSolutionIndexes() } /** - * @param ilRandomArrayElementProvider $shuffler + * @param Transformation $shuffler * @param null | array $combinations * @return string */ - public function getBestSolutionOutput(ilRandomArrayElementProvider $shuffler, $combinations = null) + public function getBestSolutionOutput(Transformation $shuffler, $combinations = null) { global $DIC; $lng = $DIC['lng']; diff --git a/Modules/TestQuestionPool/classes/class.assClozeTest.php b/Modules/TestQuestionPool/classes/class.assClozeTest.php index 98bc43b08025..b2e590bd6eb1 100755 --- a/Modules/TestQuestionPool/classes/class.assClozeTest.php +++ b/Modules/TestQuestionPool/classes/class.assClozeTest.php @@ -1,6 +1,8 @@ start_tag = "[gap]"; $this->end_tag = "[/gap]"; @@ -130,6 +136,7 @@ public function __construct( $this->identical_scoring = 1; $this->gap_combinations_exists = false; $this->gap_combinations = array(); + $this->randomGroup = $DIC->refinery()->random(); } /** @@ -2010,7 +2017,7 @@ public function isAddableAnswerOptionValue(int $qIndex, string $answerOptionValu return false; } - foreach ($gap->getItems(new ilDeterministicArrayElementProvider()) as $item) { + foreach ($gap->getItems($this->randomGroup->dontShuffle()) as $item) { if ($item->getAnswertext() == $answerOptionValue) { return false; } diff --git a/Modules/TestQuestionPool/classes/class.assClozeTestGUI.php b/Modules/TestQuestionPool/classes/class.assClozeTestGUI.php index 803c5170eca7..1642fef8b0c5 100755 --- a/Modules/TestQuestionPool/classes/class.assClozeTestGUI.php +++ b/Modules/TestQuestionPool/classes/class.assClozeTestGUI.php @@ -1,6 +1,9 @@ object = new assClozeTest(); if ($id >= 0) { $this->object->loadFromDb($id); } + + $this->randomGroup = $DIC->refinery()->random(); } public function getCommand($cmd) @@ -1432,7 +1441,7 @@ public function getAggregatedAnswersView($relevant_answers) if ($gap->type == CLOZE_TEXT) { $present_elements = array(); - foreach ($gap->getItems(new ilArrayElementShuffler()) as $item) { + foreach ($gap->getItems($this->randomGroup->shuffleArray(new RandomSeed())) as $item) { /** @var assAnswerCloze $item */ $present_elements[] = $item->getAnswertext(); } @@ -1577,7 +1586,7 @@ protected function getAnswerTextLabel($gapIndex, $answer) case CLOZE_SELECT: - $items = $gap->getItems(new ilDeterministicArrayElementProvider()); + $items = $gap->getItems($this->randomGroup->dontShuffle()); return $items[$answer]->getAnswertext(); } } @@ -1593,7 +1602,7 @@ protected function completeAddAnswerAction($answers, $questionIndex) foreach ($answers as $key => $ans) { $found = false; - foreach ($gap->getItems(new ilDeterministicArrayElementProvider()) as $item) { + foreach ($gap->getItems($this->randomGroup->dontShuffle()) as $item) { if ($ans['answer'] !== $item->getAnswerText()) { continue; } diff --git a/Modules/TestQuestionPool/classes/class.assKprimChoiceGUI.php b/Modules/TestQuestionPool/classes/class.assKprimChoiceGUI.php index b3f14e67cc18..5ea4171e36fe 100644 --- a/Modules/TestQuestionPool/classes/class.assKprimChoiceGUI.php +++ b/Modules/TestQuestionPool/classes/class.assKprimChoiceGUI.php @@ -707,7 +707,7 @@ protected function getParticipantsAnswerKeySequence() $choiceKeys = array_keys($this->object->getAnswers()); if ($this->object->isShuffleAnswersEnabled()) { - $choiceKeys = $this->object->getShuffler()->shuffle($choiceKeys); + $choiceKeys = $this->object->getShuffler()->transform($choiceKeys)->value(); } return $choiceKeys; diff --git a/Modules/TestQuestionPool/classes/class.assMatchingQuestion.php b/Modules/TestQuestionPool/classes/class.assMatchingQuestion.php index 68a179179cd0..92e1960582e9 100755 --- a/Modules/TestQuestionPool/classes/class.assMatchingQuestion.php +++ b/Modules/TestQuestionPool/classes/class.assMatchingQuestion.php @@ -1,6 +1,9 @@ matchingpairs = array(); $this->matching_type = $matching_type; $this->terms = array(); $this->definitions = array(); + $this->randomGroup = $DIC->refinery()->random(); } /** @@ -1380,14 +1388,11 @@ public function toJSON() : string 'onenotcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), false)), 'allcorrect' => $this->formatSAQuestion($this->feedbackOBJ->getGenericFeedbackTestPresentation($this->getId(), true)) ); - - require_once 'Services/Randomization/classes/class.ilArrayElementShuffler.php'; - $this->setShuffler(new ilArrayElementShuffler()); - $seed = $this->getShuffler()->getSeed(); + + $this->setShuffler($this->randomGroup->shuffleArray(new RandomSeed())); $terms = array(); - $this->getShuffler()->setSeed($this->getShuffler()->buildSeedFromString($seed . 'terms')); - foreach ($this->getShuffler()->shuffle($this->getTerms()) as $term) { + foreach ($this->getShuffler()->transform($this->getTerms())->value() as $term) { $terms[] = array( "text" => $this->formatSAQuestion($term->text), "id" => (int) $this->getId() . $term->identifier @@ -1403,8 +1408,7 @@ public function toJSON() : string // when the second one (the copy) is answered. $definitions = array(); - $this->getShuffler()->setSeed($this->getShuffler()->buildSeedFromString($seed . 'definitions')); - foreach ($this->getShuffler()->shuffle($this->getDefinitions()) as $def) { + foreach ($this->getShuffler()->transform($this->getDefinitions())->value() as $def) { $definitions[] = array( "text" => $this->formatSAQuestion((string) $def->text), "id" => (int) $this->getId() . $def->identifier diff --git a/Modules/TestQuestionPool/classes/class.assMatchingQuestionGUI.php b/Modules/TestQuestionPool/classes/class.assMatchingQuestionGUI.php index eb46dc5d0c63..d3d73b34d6c4 100755 --- a/Modules/TestQuestionPool/classes/class.assMatchingQuestionGUI.php +++ b/Modules/TestQuestionPool/classes/class.assMatchingQuestionGUI.php @@ -632,18 +632,14 @@ public function getPreview($show_question_only = false, $showInlineFeedback = fa $definitions = $this->object->getDefinitions(); switch ($this->object->getShuffle()) { case 1: - $seed = $this->object->getShuffler()->getSeed(); - $this->object->getShuffler()->setSeed($seed . '1'); - $terms = $this->object->getShuffler()->shuffle($terms); - $this->object->getShuffler()->setSeed($seed . '2'); - $definitions = $this->object->getShuffler()->shuffle($definitions); - $this->object->getShuffler()->setSeed($seed); + $terms = $this->object->getShuffler()->transform($terms)->value(); + $definitions = $this->object->getShuffler()->transform($definitions)->value(); break; case 2: - $terms = $this->object->getShuffler()->shuffle($terms); + $terms = $this->object->getShuffler()->transform($terms)->value(); break; case 3: - $definitions = $this->object->getShuffler()->shuffle($definitions); + $definitions = $this->object->getShuffler()->transform($definitions)->value(); break; } @@ -828,25 +824,21 @@ public function getTestOutput($active_id, $pass, $is_postponed = false, $user_po $definitions = $this->object->getDefinitions(); switch ($this->object->getShuffle()) { case 1: - $seed = $this->object->getShuffler()->getSeed(); - $this->object->getShuffler()->setSeed($seed . '1'); - $terms = $this->object->getShuffler()->shuffle($terms); + $terms = $this->object->getShuffler()->transform($terms)->value(); if (count($solutions)) { $definitions = $this->sortDefinitionsBySolution($solutions, $definitions); } else { - $this->object->getShuffler()->setSeed($seed . '2'); - $definitions = $this->object->getShuffler()->shuffle($definitions); + $definitions = $this->object->getShuffler()->transform($definitions)->value(); } - $this->object->getShuffler()->setSeed($seed); break; case 2: - $terms = $this->object->getShuffler()->shuffle($terms); + $terms = $this->object->getShuffler()->transform($terms)->value(); break; case 3: if (count($solutions)) { $definitions = $this->sortDefinitionsBySolution($solutions, $definitions); } else { - $definitions = $this->object->getShuffler()->shuffle($definitions); + $definitions = $this->object->getShuffler()->transform($definitions)->value(); } break; } diff --git a/Modules/TestQuestionPool/classes/class.assMultipleChoiceGUI.php b/Modules/TestQuestionPool/classes/class.assMultipleChoiceGUI.php index 03ad9ca04c02..5fd6a572ba74 100755 --- a/Modules/TestQuestionPool/classes/class.assMultipleChoiceGUI.php +++ b/Modules/TestQuestionPool/classes/class.assMultipleChoiceGUI.php @@ -737,7 +737,7 @@ public function getChoiceKeys() $choiceKeys = array_keys($this->object->answers); if ($this->object->getShuffle()) { - $choiceKeys = $this->object->getShuffler()->shuffle($choiceKeys); + $choiceKeys = $this->object->getShuffler()->transform($choiceKeys)->value(); } return $choiceKeys; diff --git a/Modules/TestQuestionPool/classes/class.assOrderingHorizontal.php b/Modules/TestQuestionPool/classes/class.assOrderingHorizontal.php index 1f8555ba1616..a1f3b46c446a 100644 --- a/Modules/TestQuestionPool/classes/class.assOrderingHorizontal.php +++ b/Modules/TestQuestionPool/classes/class.assOrderingHorizontal.php @@ -524,7 +524,7 @@ public function getOrderingElements() public function getRandomOrderingElements() { $elements = $this->getOrderingElements(); - $elements = $this->getShuffler()->shuffle($elements); + $elements = $this->getShuffler()->transform($elements)->value(); return $elements; } diff --git a/Modules/TestQuestionPool/classes/class.assOrderingQuestion.php b/Modules/TestQuestionPool/classes/class.assOrderingQuestion.php index bf1448de988a..d3a7836a038c 100755 --- a/Modules/TestQuestionPool/classes/class.assOrderingQuestion.php +++ b/Modules/TestQuestionPool/classes/class.assOrderingQuestion.php @@ -544,9 +544,9 @@ public function getSolutionOrderingElementList($indexedSolutionValues) */ public function getShuffledOrderingElementList() { - $shuffledRandomIdentifierIndex = $this->getShuffler()->shuffle( + $shuffledRandomIdentifierIndex = $this->getShuffler()->transform( $this->getOrderingElementList()->getRandomIdentifierIndex() - ); + )->value(); $shuffledElementList = $this->getOrderingElementList()->getClone(); $shuffledElementList->reorderByRandomIdentifiers($shuffledRandomIdentifierIndex); @@ -1163,7 +1163,7 @@ public function toJSON() : string $answers[$counter] = $orderingElement->getContent(); $counter++; } - $answers = $this->getShuffler()->shuffle($answers); + $answers = $this->getShuffler()->transform($answers)->value(); $arr = array(); foreach ($answers as $order => $answer) { array_push($arr, array( diff --git a/Modules/TestQuestionPool/classes/class.assQuestion.php b/Modules/TestQuestionPool/classes/class.assQuestion.php index 08cace667609..6ebaf63e4cb7 100755 --- a/Modules/TestQuestionPool/classes/class.assQuestion.php +++ b/Modules/TestQuestionPool/classes/class.assQuestion.php @@ -1,6 +1,8 @@ array('png'), 'image/gif' => array('gif') ); - + /** * assQuestion constructor */ @@ -212,7 +214,7 @@ public function __construct( $this->questionActionCmd = 'handleQuestionAction'; - $this->shuffler = new ilDeterministicArrayElementProvider(); + $this->shuffler = $DIC->refinery()->random()->dontShuffle(); $this->lifecycle = ilAssQuestionLifecycle::getDraftInstance(); } @@ -360,12 +362,12 @@ public static function getAllowedImageMaterialFileExtensions() : array return array_unique($extensions); } - public function getShuffler() : ilRandomArrayElementProvider + public function getShuffler() : Transformation { return $this->shuffler; } - public function setShuffler(ilRandomArrayElementProvider $shuffler) : void + public function setShuffler(Transformation $shuffler) : void { $this->shuffler = $shuffler; } diff --git a/Modules/TestQuestionPool/classes/class.assSingleChoiceGUI.php b/Modules/TestQuestionPool/classes/class.assSingleChoiceGUI.php index 6bd9c78d2774..6e6c435b8acd 100755 --- a/Modules/TestQuestionPool/classes/class.assSingleChoiceGUI.php +++ b/Modules/TestQuestionPool/classes/class.assSingleChoiceGUI.php @@ -610,7 +610,7 @@ public function getChoiceKeys() $choiceKeys = array_keys($this->object->answers); if ($this->object->getShuffle()) { - $choiceKeys = $this->object->getShuffler()->shuffle($choiceKeys); + $choiceKeys = $this->object->getShuffler()->transform($choiceKeys)->value(); } return $choiceKeys; diff --git a/Modules/TestQuestionPool/classes/class.ilAssQuestionPreviewGUI.php b/Modules/TestQuestionPool/classes/class.ilAssQuestionPreviewGUI.php index 6af31ee05dc3..471672fb1a47 100644 --- a/Modules/TestQuestionPool/classes/class.ilAssQuestionPreviewGUI.php +++ b/Modules/TestQuestionPool/classes/class.ilAssQuestionPreviewGUI.php @@ -1,6 +1,10 @@ @@ -82,8 +86,10 @@ class ilAssQuestionPreviewGUI * @var ilAssQuestionPreviewHintTracking */ protected $hintTracking; + + private RandomGroup $randomGroup; - public function __construct(ilCtrl $ctrl, ilTabsGUI $tabs, ilGlobalTemplateInterface $tpl, ilLanguage $lng, ilDBInterface $db, ilObjUser $user) + public function __construct(ilCtrl $ctrl, ilTabsGUI $tabs, ilGlobalTemplateInterface $tpl, ilLanguage $lng, ilDBInterface $db, ilObjUser $user, RandomGroup $randomGroup) { $this->ctrl = $ctrl; $this->tabs = $tabs; @@ -91,6 +97,7 @@ public function __construct(ilCtrl $ctrl, ilTabsGUI $tabs, ilGlobalTemplateInter $this->lng = $lng; $this->db = $db; $this->user = $user; + $this->randomGroup = $randomGroup; } public function initQuestion($questionId, $parentObjId) @@ -543,20 +550,15 @@ public function gatewayShowHintListCmd() } /** - * @return ilArrayElementShuffler + * @return Transformation */ private function getQuestionAnswerShuffler() { - require_once 'Services/Randomization/classes/class.ilArrayElementShuffler.php'; - $shuffler = new ilArrayElementShuffler(); - if (!$this->previewSession->randomizerSeedExists()) { - $this->previewSession->setRandomizerSeed($shuffler->buildRandomSeed()); + $this->previewSession->setRandomizerSeed((new RandomSeed())->createSeed()); } - - $shuffler->setSeed($this->previewSession->getRandomizerSeed()); - - return $shuffler; + + return $this->randomGroup->shuffleArray(new GivenSeed($this->previewSession->getRandomizerSeed())); } protected function populateNotesPanel(ilTemplate $tpl, $notesPanelHTML) diff --git a/Modules/TestQuestionPool/classes/class.ilObjQuestionPoolGUI.php b/Modules/TestQuestionPool/classes/class.ilObjQuestionPoolGUI.php index d4f5be7a6a85..624d20c5b1be 100755 --- a/Modules/TestQuestionPool/classes/class.ilObjQuestionPoolGUI.php +++ b/Modules/TestQuestionPool/classes/class.ilObjQuestionPoolGUI.php @@ -92,6 +92,7 @@ public function executeCommand() $ilDB = $DIC['ilDB']; $ilPluginAdmin = $DIC['ilPluginAdmin']; $ilias = $DIC['ilias']; + $randomGroup = $DIC->refinery()->random(); $writeAccess = $ilAccess->checkAccess("write", "", $_GET["ref_id"]); @@ -162,7 +163,7 @@ public function executeCommand() $this->ctrl->saveParameter($this, "q_id"); require_once 'Modules/TestQuestionPool/classes/class.ilAssQuestionPreviewGUI.php'; - $gui = new ilAssQuestionPreviewGUI($this->ctrl, $this->tabs_gui, $this->tpl, $this->lng, $ilDB, $ilUser); + $gui = new ilAssQuestionPreviewGUI($this->ctrl, $this->tabs_gui, $this->tpl, $this->lng, $ilDB, $ilUser, $randomGroup); $gui->initQuestion((int) $_GET['q_id'], $this->object->getId()); $gui->initPreviewSettings($this->object->getRefId()); diff --git a/Modules/TestQuestionPool/classes/export/qti12/class.assClozeTestExport.php b/Modules/TestQuestionPool/classes/export/qti12/class.assClozeTestExport.php index 3635cb9c76b6..789a06d4f4d1 100644 --- a/Modules/TestQuestionPool/classes/export/qti12/class.assClozeTestExport.php +++ b/Modules/TestQuestionPool/classes/export/qti12/class.assClozeTestExport.php @@ -1,6 +1,8 @@ randomGroup = $DIC->refinery()->random(); + } + /** * Returns a QTI xml representation of the question * @@ -144,7 +157,7 @@ public function toXML($a_include_header = true, $a_include_binary = true, $a_shu $a_xml_writer->xmlStartTag("render_choice", $attrs); // add answers - foreach ($gap->getItems(new ilDeterministicArrayElementProvider()) as $answeritem) { + foreach ($gap->getItems($this->randomGroup->dontShuffle()) as $answeritem) { $attrs = array( "ident" => $answeritem->getOrder() ); @@ -249,7 +262,7 @@ public function toXML($a_include_header = true, $a_include_binary = true, $a_shu $gap = $this->object->getGap($i); switch ($gap->getType()) { case CLOZE_SELECT: - foreach ($gap->getItems(new ilDeterministicArrayElementProvider()) as $answer) { + foreach ($gap->getItems($this->randomGroup->dontShuffle()) as $answer) { $attrs = array( "continue" => "Yes" ); @@ -280,7 +293,7 @@ public function toXML($a_include_header = true, $a_include_binary = true, $a_shu break; case CLOZE_NUMERIC: case CLOZE_TEXT: - foreach ($gap->getItems(new ilDeterministicArrayElementProvider()) as $answer) { + foreach ($gap->getItems($this->randomGroup->dontShuffle()) as $answer) { $attrs = array( "continue" => "Yes" ); diff --git a/Modules/TestQuestionPool/classes/feedback/class.ilAssClozeTestFeedback.php b/Modules/TestQuestionPool/classes/feedback/class.ilAssClozeTestFeedback.php index b1fdf84bda26..eb352cdea876 100644 --- a/Modules/TestQuestionPool/classes/feedback/class.ilAssClozeTestFeedback.php +++ b/Modules/TestQuestionPool/classes/feedback/class.ilAssClozeTestFeedback.php @@ -1,6 +1,8 @@ getItems(new ilDeterministicArrayElementProvider()) as $item) { + foreach ($gap->getItems($this->randomGroup()->dontShuffle()) as $item) { $answers[] = '"' . $item->getAnswertext() . '"'; } @@ -191,7 +193,7 @@ protected function completeFormPropsForFeedbackModeGapAnswers(ilRadioOption $fbM protected function completeFbPropsForTextGap(ilRadioOption $fbModeOpt, assClozeGap $gap, int $gapIndex) : void { - foreach ($gap->getItems(new ilDeterministicArrayElementProvider()) as $answerIndex => $item) { + foreach ($gap->getItems($this->randomGroup()->dontShuffle()) as $answerIndex => $item) { $propertyLabel = $this->questionOBJ->prepareTextareaOutput( $this->buildTextGapGivenAnswerFeedbackLabel($gapIndex, $item), true @@ -235,7 +237,7 @@ protected function completeFbPropsForTextGap(ilRadioOption $fbModeOpt, assClozeG protected function completeFbPropsForSelectGap(ilRadioOption $fbModeOpt, assClozeGap $gap, int $gapIndex) : void { - foreach ($gap->getItems(new ilDeterministicArrayElementProvider()) as $optIndex => $item) { + foreach ($gap->getItems($this->randomGroup()->dontShuffle()) as $optIndex => $item) { $propertyLabel = $this->questionOBJ->prepareTextareaOutput( $this->buildSelectGapOptionFeedbackLabel($gapIndex, $item), true @@ -397,7 +399,7 @@ protected function initFeedbackFieldsPerGapAnswers(ilPropertyFormGUI $form) : vo protected function initFbPropsForTextGap(ilPropertyFormGUI $form, assClozeGap $gap, int $gapIndex) : void { - foreach ($gap->getItems(new ilDeterministicArrayElementProvider()) as $answerIndex => $item) { + foreach ($gap->getItems($this->randomGroup()->dontShuffle()) as $answerIndex => $item) { $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, $answerIndex); $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, $answerIndex); $form->getItemByPostVar($postVar)->setValue($value); @@ -414,7 +416,7 @@ protected function initFbPropsForTextGap(ilPropertyFormGUI $form, assClozeGap $g protected function initFbPropsForSelectGap(ilPropertyFormGUI $form, assClozeGap $gap, int $gapIndex) : void { - foreach ($gap->getItems(new ilDeterministicArrayElementProvider()) as $optIndex => $item) { + foreach ($gap->getItems($this->randomGroup()->dontShuffle()) as $optIndex => $item) { $value = $this->getSpecificAnswerFeedbackFormValue($gapIndex, $optIndex); $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, $optIndex); $form->getItemByPostVar($postVar)->setValue($value); @@ -514,7 +516,7 @@ protected function saveFeedbackFieldsPerGapAnswers(ilPropertyFormGUI $form) : vo protected function saveFbPropsForTextGap(ilPropertyFormGUI $form, assClozeGap $gap, int $gapIndex) : void { - foreach ($gap->getItems(new ilDeterministicArrayElementProvider()) as $answerIndex => $item) { + foreach ($gap->getItems($this->randomGroup()->dontShuffle()) as $answerIndex => $item) { $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, $answerIndex); $value = $form->getItemByPostVar($postVar)->getValue(); $this->saveSpecificAnswerFeedbackContent( @@ -546,7 +548,7 @@ protected function saveFbPropsForTextGap(ilPropertyFormGUI $form, assClozeGap $g protected function saveFbPropsForSelectGap(ilPropertyFormGUI $form, assClozeGap $gap, int $gapIndex) : void { - foreach ($gap->getItems(new ilDeterministicArrayElementProvider()) as $optIndex => $item) { + foreach ($gap->getItems($this->randomGroup()->dontShuffle()) as $optIndex => $item) { $postVar = $this->buildPostVarForFbFieldPerGapAnswers($gapIndex, $optIndex); $value = $form->getItemByPostVar($postVar)->getValue(); $this->saveSpecificAnswerFeedbackContent( @@ -792,7 +794,7 @@ public function determineAnswerIndexForAnswerValue(assClozeGap $gap, int $answer return self::FB_TEXT_GAP_EMPTY_INDEX; } - $items = $gap->getItems(new ilDeterministicArrayElementProvider()); + $items = $gap->getItems($this->randomGroup()->dontShuffle()); foreach ($items as $answerIndex => $answer) { /* @var assAnswerCloze $answer */ @@ -821,7 +823,7 @@ public function determineAnswerIndexForAnswerValue(assClozeGap $gap, int $answer /* @var assAnswerCloze $item */ - $item = current($gap->getItems(new ilDeterministicArrayElementProvider())); + $item = current($gap->getItems($this->randomGroup()->dontShuffle())); if ($answerValue == $item->getAnswertext()) { return self::FB_NUMERIC_GAP_VALUE_HIT_INDEX; @@ -850,4 +852,11 @@ public function determineAnswerIndexForAnswerValue(assClozeGap $gap, int $answer //} } } + + private function randomGroup() : RandomGroup + { + global $DIC; + + return $DIC->refinery()->random(); + } } diff --git a/Modules/TestQuestionPool/classes/questions/LogicalAnswerCompare/ilAssLacCompositeValidator.php b/Modules/TestQuestionPool/classes/questions/LogicalAnswerCompare/ilAssLacCompositeValidator.php index 6be3b700eb0f..c14c56f9fe97 100644 --- a/Modules/TestQuestionPool/classes/questions/LogicalAnswerCompare/ilAssLacCompositeValidator.php +++ b/Modules/TestQuestionPool/classes/questions/LogicalAnswerCompare/ilAssLacCompositeValidator.php @@ -1,5 +1,7 @@ object_loader = $object_loader; + $this->randomGroup = $DIC->refinery()->random(); } public function validate(ilAssLacAbstractComposite $composite) @@ -240,6 +247,6 @@ private function checkOperatorExistForExpression($operators, $answer_expression, protected function getNonShuffler() { - return new ilDeterministicArrayElementProvider(); + return $this->randomGroup->dontShuffle(); } } diff --git a/Modules/TestQuestionPool/test/assBaseTestCase.php b/Modules/TestQuestionPool/test/assBaseTestCase.php index 7c1a2bfad56a..58d091d6f2eb 100644 --- a/Modules/TestQuestionPool/test/assBaseTestCase.php +++ b/Modules/TestQuestionPool/test/assBaseTestCase.php @@ -2,6 +2,8 @@ /* Copyright (c) 1998-2018 ILIAS open source, Extended GPL, see docs/LICENSE */ use PHPUnit\Framework\TestCase; +use ILIAS\Refinery\Factory as RefineryFactory; +use ILIAS\Refinery\Random\Group as RandomGroup; /** * Class assBaseTestCase @@ -48,6 +50,10 @@ protected function setUp() : void $DIC['rbacsystem'] = $rbacsystem_mock; $GLOBALS['rbacsystem'] = $rbacsystem_mock; + $refineryMock = $this->getMockBuilder(RefineryFactory::class)->disableOriginalConstructor()->getMock(); + $refineryMock->expects(self::any())->method('random')->willReturn($this->getMockBuilder(RandomGroup::class)->getMock()); + $DIC['refinery'] = $refineryMock; + parent::setUp(); } diff --git a/Modules/TestQuestionPool/test/assClozeGapTest.php b/Modules/TestQuestionPool/test/assClozeGapTest.php index 2ed904b33c1d..55eb372e742a 100644 --- a/Modules/TestQuestionPool/test/assClozeGapTest.php +++ b/Modules/TestQuestionPool/test/assClozeGapTest.php @@ -1,6 +1,9 @@ createMock('ilUtil', array('stripSlashes'), array(), '', false); $util_mock->expects($this->any())->method('stripSlashes')->will($this->returnArgument(0)); $this->setGlobalVariable('ilUtils', $util_mock); @@ -99,11 +101,16 @@ public function test_arrayShuffle_shouldNotReturnArrayUnshuffled() 'Meyer', 'Jansen', 'Heyser', 'Becker'); $instance->items = $the_unexpected; $instance->setShuffle(true); - - $actual = $instance->getItems(new ilArrayElementShuffler); + $theExpected = ['hua', 'haaa', 'some random values']; + + $transformationMock = $this->getMockBuilder(Transformation::class)->getMock(); + $mockEffect = $this->getMockBuilder(Effect::class)->getMock(); + $mockEffect->expects(self::once())->method('value')->willReturn($theExpected); + $transformationMock->expects(self::once())->method('transform')->with($the_unexpected)->willReturn($mockEffect); + $actual = $instance->getItems($transformationMock); // Assert - $this->assertNotEquals($the_unexpected, $actual); + $this->assertEquals($theExpected, $actual); } public function test_addGetItem_shouldReturnValueUnchanged() @@ -201,7 +208,9 @@ public function test_getItems_shouldReturnItemsAdded() $instance->addItem($item2); $instance->addItem($item3); $instance->addItem($item4); - $actual = $instance->getItems(new ilArrayElementShuffler); + $transformationMock = $this->getMockBuilder(Transformation::class)->getMock(); + $transformationMock->expects(self::never())->method('transform'); + $actual = $instance->getItems($transformationMock); // Assert $this->assertEquals($expected, $actual); @@ -214,38 +223,33 @@ public function test_getItemsWithShuffle_shouldReturnItemsAddedShuffled() $instance = new assClozeGap(0); // 0 - text gap $instance->setShuffle(true); require_once './Modules/TestQuestionPool/classes/class.assAnswerCloze.php'; - $item1 = new assAnswerCloze('Bert', 1.0, 0); - $item2 = new assAnswerCloze('Fred', 1.0, 1); - $item3 = new assAnswerCloze('Karl', 1.0, 2); - $item4 = new assAnswerCloze('Esther', 1.0, 3); - $item5 = new assAnswerCloze('Herbert', 1.0, 4); - $item6 = new assAnswerCloze('Karina', 1.0, 5); - $item7 = new assAnswerCloze('Helmut', 1.0, 6); - $item8 = new assAnswerCloze('Kerstin', 1.0, 7); - $expected = array($item1, $item2, $item3, $item4, $item5, $item6, $item7, $item8); + $expected = [ + new assAnswerCloze('Bert', 1.0, 0), + new assAnswerCloze('Fred', 1.0, 1), + new assAnswerCloze('Karl', 1.0, 2), + new assAnswerCloze('Esther', 1.0, 3), + new assAnswerCloze('Herbert', 1.0, 4), + new assAnswerCloze('Karina', 1.0, 5), + new assAnswerCloze('Helmut', 1.0, 6), + new assAnswerCloze('Kerstin', 1.0, 7), + ]; + + $shuffledArray = ['some shuffled array', 'these values dont matter']; // Act - $instance->addItem($item1); - $instance->addItem($item2); - $instance->addItem($item3); - $instance->addItem($item4); - $instance->addItem($item5); - $instance->addItem($item6); - $instance->addItem($item7); - $instance->addItem($item8); - $actual = $instance->getItems(new ilArrayElementShuffler); + foreach ($expected as $item) { + $instance->addItem($item); + } + + $transformationMock = $this->getMockBuilder(Transformation::class)->getMock(); + $mockEffect = $this->getMockBuilder(Effect::class)->getMock(); + $mockEffect->expects(self::once())->method('value')->willReturn($shuffledArray); + $transformationMock->expects(self::once())->method('transform')->with($expected)->willReturn($mockEffect); + $actual = $instance->getItems($transformationMock); // Assert - $this->assertTrue(is_array($actual)); - $this->assertTrue(in_array($item1, $actual)); - $this->assertTrue(in_array($item2, $actual)); - $this->assertTrue(in_array($item3, $actual)); - $this->assertTrue(in_array($item4, $actual)); - $this->assertTrue(in_array($item5, $actual)); - $this->assertTrue(in_array($item6, $actual)); - $this->assertTrue(in_array($item7, $actual)); - $this->assertTrue(in_array($item8, $actual)); - $this->assertNotEquals($expected, $actual); + + $this->assertEquals($shuffledArray, $actual); } public function test_getItemsRaw_shouldReturnItemsAdded() @@ -527,7 +531,7 @@ public function test_getBestSolutionOutput_shouldReturnBestSolutionOutput_CaseTe $expected = 'Esther'; // Act - $actual = $instance->getBestSolutionOutput(new ilArrayElementShuffler); + $actual = $instance->getBestSolutionOutput($this->getDummyTransformationMock()); // Assert $this->assertEquals($expected, $actual); @@ -563,7 +567,7 @@ public function test_getBestSolutionOutput_shouldReturnBestSolutionOutput_CaseTe $expected2 = 'Esther or Karl'; // Act - $actual = $instance->getBestSolutionOutput(new ilArrayElementShuffler); + $actual = $instance->getBestSolutionOutput($this->getDummyTransformationMock()); // Assert $this->assertTrue(($actual == $expected1) || ($actual == $expected2)); @@ -597,7 +601,7 @@ public function test_getBestSolutionOutput_shouldReturnBestSolutionOutput_CaseNu $expected = 100; // Act - $actual = $instance->getBestSolutionOutput(new ilArrayElementShuffler); + $actual = $instance->getBestSolutionOutput($this->getDummyTransformationMock()); // Assert $this->assertEquals($expected, $actual); @@ -631,9 +635,24 @@ public function test_getBestSolutionOutput_shouldReturnEmptyStringOnUnknownType_ $expected = ''; // Act - $actual = $instance->getBestSolutionOutput(new ilArrayElementShuffler); + $actual = $instance->getBestSolutionOutput($this->getDummyTransformationMock()); // Assert $this->assertEquals($expected, $actual); } + + private function getDummyTransformationMock() : Transformation + { + + $callback = function (array $array) : Effect { + $mockEffect = $this->getMockBuilder(Effect::class)->getMock(); + $mockEffect->expects(self::once())->method('value')->willReturn($array); + + return $mockEffect; + }; + $transformationMock = $this->getMockBuilder(Transformation::class)->getMock(); + $transformationMock->expects(self::any())->method('transform')->willReturnCallback($callback); + + return $transformationMock; + } } diff --git a/Modules/TestQuestionPool/test/assClozeSelectGapTest.php b/Modules/TestQuestionPool/test/assClozeSelectGapTest.php index 71e7ba588890..142f2f63a702 100644 --- a/Modules/TestQuestionPool/test/assClozeSelectGapTest.php +++ b/Modules/TestQuestionPool/test/assClozeSelectGapTest.php @@ -3,6 +3,9 @@ require_once __DIR__ . "/assBaseTestCase.php"; +use ILIAS\Refinery\Transformation; +use ILIAS\Refinery\Effect\Effect; + /** * Unit tests * @@ -54,10 +57,14 @@ public function test_arrayShuffle_shouldShuffleArray() // Arrange require_once './Modules/TestQuestionPool/classes/class.assClozeSelectGap.php'; $instance = new assClozeSelectGap(1); // 1 - select gap - $expected = array(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20); + $expected = ['shfksdfs', 'sfsdf', 'sdfsdfdf']; - $actual = $instance->getItems(new ilArrayElementShuffler()); - $this->assertNotEquals($expected, $actual); + $mockEffect = $this->getMockBuilder(Effect::class)->getMock(); + $mockEffect->expects(self::once())->method('value')->willReturn($expected); + $transformationMock = $this->getMockBuilder(Transformation::class)->getMock(); + $transformationMock->expects(self::once())->method('transform')->willReturn($mockEffect); + $actual = $instance->getItems($transformationMock); + $this->assertEquals($expected, $actual); } public function test_getItemswithShuffle_shouldReturnShuffledItems() @@ -89,11 +96,13 @@ public function test_getItemswithShuffle_shouldReturnShuffledItems() $sequence = [$item1, $item3, $item2, $item4, $item5, $item6, $item7, $item8]; $expectedSequence = array_reverse($sequence); - $randomElmProvider = $this->getMockBuilder(ilRandomArrayElementProvider::class)->getMock(); + $mockEffect = $this->getMockBuilder(Effect::class)->getMock(); + $mockEffect->expects(self::once())->method('value')->willReturn($expectedSequence); + $randomElmProvider = $this->getMockBuilder(Transformation::class)->getMock(); $randomElmProvider->expects($this->once()) - ->method('shuffle') + ->method('transform') ->with($sequence) - ->willReturn($expectedSequence); + ->willReturn($mockEffect); $actual = $instance->getItems($randomElmProvider); $this->assertEquals($actual, $expectedSequence); @@ -118,7 +127,14 @@ public function test_getItemswithoutShuffle_shouldReturnItemsInOrder() $instance->setType(false); $expected = array($item1, $item2, $item3, $item4); - $actual = $instance->getItems(new ilDeterministicArrayElementProvider()); + $transformationMock = $this->getMockBuilder(Transformation::class)->getMock(); + $transformationMock->expects(self::once())->method('transform')->willReturnCallback(function ($value) { + $mockEffect = $this->getMockBuilder(Effect::class)->getMock(); + $mockEffect->expects(self::once())->method('value')->willReturn($value); + + return $mockEffect; + }); + $actual = $instance->getItems($transformationMock); $this->assertEquals($expected, $actual); } diff --git a/Services/Randomization/classes/class.ilArrayElementShuffler.php b/Services/Randomization/classes/class.ilArrayElementShuffler.php deleted file mode 100644 index 83b45f9ecbfc..000000000000 --- a/Services/Randomization/classes/class.ilArrayElementShuffler.php +++ /dev/null @@ -1,64 +0,0 @@ - - * @author Michael Jansen - * @package Services/Randomization - */ -class ilArrayElementShuffler extends ilBaseRandomElementProvider implements ilRandomArrayElementProvider -{ - private function isMtRandomizerAvailable() : bool - { - return function_exists('mt_srand') && function_exists('mt_rand'); - } - - protected function getInitialSeed() : int - { - [$usec, $sec] = explode(' ', microtime()); - - return (int) ($sec + ($usec * 100000)); - } - - private function initSeed(int $seed) : void - { - if ($this->isMtRandomizerAvailable()) { - mt_srand($seed); - } else { - srand($seed); - } - } - - private function shuffleArray(array $array) : array - { - if ($this->isMtRandomizerAvailable()) { - return $this->mtShuffle($array); - } - - shuffle($array); - - return $array; - } - - private function mtShuffle(array $orderedArray) : array - { - $shuffledArray = []; - - while (count($orderedArray) > 0) { - $key = mt_rand(0, (count($orderedArray) - 1)); - $splice = array_splice($orderedArray, $key, 1); - $shuffledArray[] = current($splice); - } - - return $shuffledArray; - } - - public function shuffle(array $array) : array - { - $this->initSeed($this->getSeed()); - $array = $this->shuffleArray($array); - $this->initSeed($this->getInitialSeed()); - - return $array; - } -} diff --git a/Services/Randomization/classes/class.ilBaseRandomElementProvider.php b/Services/Randomization/classes/class.ilBaseRandomElementProvider.php deleted file mode 100644 index 310ee1fecbb9..000000000000 --- a/Services/Randomization/classes/class.ilBaseRandomElementProvider.php +++ /dev/null @@ -1,32 +0,0 @@ - - */ -abstract class ilBaseRandomElementProvider implements ilRandomArrayElementProvider -{ - protected int $seed; - - public function __construct() - { - $this->setSeed($this->getInitialSeed()); - } - - abstract protected function getInitialSeed() : int; - - public function getSeed() : int - { - return $this->seed; - } - - public function setSeed(int $seed) : void - { - $this->seed = $seed; - } - - public function buildSeedFromString(string $string) : int - { - return (int) hexdec(substr(md5($string), 0, 10)); - } -} diff --git a/Services/Randomization/classes/class.ilDeterministicArrayElementProvider.php b/Services/Randomization/classes/class.ilDeterministicArrayElementProvider.php deleted file mode 100644 index 35b560a7f86c..000000000000 --- a/Services/Randomization/classes/class.ilDeterministicArrayElementProvider.php +++ /dev/null @@ -1,22 +0,0 @@ - - * @author Michael Jansen - * @package Services/Randomization - */ -class ilDeterministicArrayElementProvider extends ilBaseRandomElementProvider implements ilRandomArrayElementProvider -{ - protected function getInitialSeed() : int - { - return 1; - } - - public function shuffle(array $array) : array - { - return $array; - } -} diff --git a/Services/Randomization/interfaces/interface.ilRandomArrayElementProvider.php b/Services/Randomization/interfaces/interface.ilRandomArrayElementProvider.php deleted file mode 100644 index d00da1346c9a..000000000000 --- a/Services/Randomization/interfaces/interface.ilRandomArrayElementProvider.php +++ /dev/null @@ -1,19 +0,0 @@ - - * @author Marvin Beym - */ -interface ilRandomArrayElementProvider -{ - public function getSeed() : int; - - public function setSeed(int $seed) : void; - - public function buildSeedFromString(string $string) : int; - - public function shuffle(array $array) : array; -} diff --git a/Services/Randomization/test/RandomizationStrategiesTest.php b/Services/Randomization/test/RandomizationStrategiesTest.php deleted file mode 100644 index 525640c4417b..000000000000 --- a/Services/Randomization/test/RandomizationStrategiesTest.php +++ /dev/null @@ -1,71 +0,0 @@ - - */ -class RandomizationStrategiesTest extends TestCase -{ - private const NUM_RUNS = 1000; - private const NUM_RANDOM_NUMBERS = 1000; - private const NUM_FINAL_ELEMENTS = 10; - - private function randomArrayElementsProvider() : Generator - { - $randomNumbers = range(0, self::NUM_RANDOM_NUMBERS); - - for ($i = 1; $i <= self::NUM_RUNS; $i++) { - $numbersOfRun = $randomNumbers; - shuffle($numbersOfRun); - $numbersOfRun = array_slice($numbersOfRun, 0, self::NUM_FINAL_ELEMENTS); - yield 'Random numbers run ' . $i => $numbersOfRun; - } - } - - public function testDeterministicElementStrategy() : void - { - $provider = new ilDeterministicArrayElementProvider(); - - foreach ($this->randomArrayElementsProvider() as $runInfo => $elements) { - $provider->setSeed(1); - $actualWithInitialSeed = $provider->shuffle($elements); - if ($elements !== $actualWithInitialSeed) { - $this->fail($runInfo . ' unexpectedly resulted in a random order of elements.'); - } - - $provider->setSeed(2); - $actualWithOtherSeed = $provider->shuffle($elements); - if ($elements !== $actualWithOtherSeed) { - $this->fail($runInfo . ' unexpectedly resulted in a random order of elements.'); - } - } - - $this->assertTrue(true, 'Shuffling elements with ' . get_class($provider) . ' resulted in output=input'); - } - - /** - * Attention: Testing random is generally a bad idea, but we assume that from the defined N runs at least 1 - * one will randomly return a random ordering of elements - */ - public function testRandomElementStrategy() : void - { - foreach ($this->randomArrayElementsProvider() as $runInfo => $elements) { - $provider = new ilArrayElementShuffler(); - $actual = $provider->shuffle($elements); - - if ($elements !== $actual) { - $this->assertTrue(true, $runInfo . ' resulted in a random ordering of elements'); - return; - } - } - - $this->fail(sprintf( - 'None of %s runs resulted in a random ordering of elements', - self::NUM_RUNS - )); - } -} diff --git a/src/Refinery/Effect/Effect.php b/src/Refinery/Effect/Effect.php new file mode 100644 index 000000000000..36517421cb6a --- /dev/null +++ b/src/Refinery/Effect/Effect.php @@ -0,0 +1,19 @@ + + */ +namespace ILIAS\Refinery\Effect; + +/** + * This interface represents a semantic measure to indicate an intentional side effect. + * This interface can be used as a return value from Transformation::transform(...) to keep a neccessary side effect out of the transformation itself. + * The interface is NOT required to have a side effect. + */ +interface Effect +{ + /** + * @return mixed // can return everything + */ + public function value(); +} diff --git a/src/Refinery/Effect/IdentityEffect.php b/src/Refinery/Effect/IdentityEffect.php new file mode 100644 index 000000000000..9c5629ad21c7 --- /dev/null +++ b/src/Refinery/Effect/IdentityEffect.php @@ -0,0 +1,18 @@ +value = $value; + } + + public function value() + { + return $this->value; + } +} diff --git a/src/Refinery/Effect/Transformation/LiftTransformation.php b/src/Refinery/Effect/Transformation/LiftTransformation.php new file mode 100644 index 000000000000..304e85be2464 --- /dev/null +++ b/src/Refinery/Effect/Transformation/LiftTransformation.php @@ -0,0 +1,60 @@ + + */ + public function transformResult($from) : Result + { + return $this->validate($from)->map([$this, 'createEffect']); + } + + /** + * @throws \InvalidArgumentException + * @return Effect + */ + public function transform($from) + { + return $this->transformResult($from)->value(); + } + + /** + * @return Result + */ + public function applyTo(Result $result) : Result + { + return $result->then([$this, 'transformResult']); + } + + /** + * @return Result + */ + public function __invoke($from) + { + return $this->transform($from); + } + + /** + * @return Result + */ + protected function validate($from) : Result + { + return new Ok($from); + } + + public function createEffect($value) : Effect + { + return new IdentityEffect($value); + } +} diff --git a/src/Refinery/Factory.php b/src/Refinery/Factory.php index 4e6c57181294..f990e3cb07bc 100644 --- a/src/Refinery/Factory.php +++ b/src/Refinery/Factory.php @@ -7,6 +7,7 @@ use ILIAS\Refinery\In; use ILIAS\Refinery\To; use ILIAS\Refinery\ByTrying; +use ILIAS\Refinery\Random\Group as RandomGroup; /** * @author Niels Theen @@ -146,4 +147,9 @@ public function byTrying(array $transformations) : ByTrying { return new ByTrying($transformations, $this->dataFactory, $this->language); } + + public function random() : RandomGroup + { + return new RandomGroup(); + } } diff --git a/src/Refinery/Random/Effect/ShuffleEffect.php b/src/Refinery/Random/Effect/ShuffleEffect.php new file mode 100644 index 000000000000..8e716150f548 --- /dev/null +++ b/src/Refinery/Random/Effect/ShuffleEffect.php @@ -0,0 +1,29 @@ +seed = $seed; + } + + /** + * @return array + */ + public function value() + { + $this->seed->seedRandomGenerator(); + $value = parent::value(); + \shuffle($value); + + return $value; + } +} diff --git a/src/Refinery/Random/Group.php b/src/Refinery/Random/Group.php new file mode 100644 index 000000000000..66844214e1fd --- /dev/null +++ b/src/Refinery/Random/Group.php @@ -0,0 +1,34 @@ + + */ +namespace ILIAS\Refinery\Random; + +use ILIAS\Refinery\Transformation; +use ILIAS\Refinery\Random\Transformation\ShuffleTransformation; +use ILIAS\Refinery\Random\Seed\Seed; +use ILIAS\Refinery\Effect\Transformation\LiftTransformation; + +class Group +{ + /** + * Get a transformation which will shuffle a given array. + * Only arrays can be supplied to the transformation. + * + * The transformation will be shuffled with the given $seed. + */ + public function shuffleArray(Seed $seed) : Transformation + { + return new ShuffleTransformation($seed); + } + + /** + * Get a transformation which will return the given value as is. + * Everything can be supplied to the transformation. + */ + public function dontShuffle() : Transformation + { + return new LiftTransformation(); + } +} diff --git a/src/Refinery/Random/Seed/GivenSeed.php b/src/Refinery/Random/Seed/GivenSeed.php new file mode 100644 index 000000000000..c2f8f5530067 --- /dev/null +++ b/src/Refinery/Random/Seed/GivenSeed.php @@ -0,0 +1,26 @@ + + */ +namespace ILIAS\Refinery\Random\Seed; + +class GivenSeed implements Seed +{ + private int $seed; + + public function __construct(int $seed) + { + $this->seed = $seed; + } + + public function seedRandomGenerator() : void + { + mt_srand($this->seed); + } + + public function getSeed() : int + { + return $this->seed(); + } +} diff --git a/src/Refinery/Random/Seed/RandomSeed.php b/src/Refinery/Random/Seed/RandomSeed.php new file mode 100644 index 000000000000..d45a81dbdeca --- /dev/null +++ b/src/Refinery/Random/Seed/RandomSeed.php @@ -0,0 +1,22 @@ + + */ +namespace ILIAS\Refinery\Random\Seed; + +class RandomSeed extends GivenSeed +{ + public function __construct() + { + parent::__construct($this->createSeed()); + } + + public function createSeed() : int + { + $array = explode(' ', microtime()); + $seed = $array[1] + ($array[0] * 100000); + + return $seed; + } +} diff --git a/src/Refinery/Random/Seed/Seed.php b/src/Refinery/Random/Seed/Seed.php new file mode 100644 index 000000000000..7dbe51d12ffa --- /dev/null +++ b/src/Refinery/Random/Seed/Seed.php @@ -0,0 +1,12 @@ + + */ +namespace ILIAS\Refinery\Random\Seed; + +interface Seed +{ + public function seedRandomGenerator() : void; + public function getSeed() : int; +} diff --git a/src/Refinery/Random/Transformation/ShuffleTransformation.php b/src/Refinery/Random/Transformation/ShuffleTransformation.php new file mode 100644 index 000000000000..8f9449197441 --- /dev/null +++ b/src/Refinery/Random/Transformation/ShuffleTransformation.php @@ -0,0 +1,41 @@ + + */ +namespace ILIAS\Refinery\Random\Transformation; + +use ILIAS\Refinery\Transformation; +use ILIAS\Data\Result; +use ILIAS\Data\Result\Ok; +use ILIAS\Data\Result\Error; +use ILIAS\Refinery\Random\Seed\Seed; +use ILIAS\Refinery\Random\Effect\ShuffleEffect; +use ILIAS\Refinery\Effect\Effect; +use ILIAS\Refinery\Effect\Transformation\LiftTransformation; + +class ShuffleTransformation extends LiftTransformation +{ + private Seed $seed; + + public function __construct(Seed $seed) + { + $this->seed = $seed; + } + + /** + * @param array $value + */ + public function createEffect($value) : Effect + { + return new ShuffleEffect($value, $this->seed); + } + + /** + * @return Result> + */ + protected function validate($from) : Result + { + return is_array($from) ? new Ok($from) : new Error('I need an array'); + } +} diff --git a/tests/Refinery/Effect/Transformation/LiftTransformationTest.php b/tests/Refinery/Effect/Transformation/LiftTransformationTest.php new file mode 100644 index 000000000000..1482cdac4354 --- /dev/null +++ b/tests/Refinery/Effect/Transformation/LiftTransformationTest.php @@ -0,0 +1,63 @@ + + */ +namespace ILIAS\Tests\Refinery\Effect\Transformation; + +use ILIAS\Data\NotOKException; +use ILIAS\Data\Result\OK; +use ILIAS\Data\Result\Error; +use ILIAS\Refinery\Effect\Transformation\LiftTransformation; +use PHPUnit\Framework\TestCase; +use ILIAS\Refinery\Effect\Effect; + +class LiftTransformationTest extends TestCase +{ + public function testTransform() : void + { + $value = 'hejaaa'; + + $actual = (new LiftTransformation())->transform($value); + + $this->assertInstanceOf(Effect::class, $actual); + $this->assertEquals($value, $actual->value()); + } + + public function testInvoke() : void + { + $value = ['bababa', 'uauauau']; + + $actual = (new LiftTransformation())($value); + + $this->assertInstanceOf(Effect::class, $actual); + $this->assertEquals($value, $actual->value()); + } + + public function testTransformResult() : void + { + $value = 678; + + $actual = (new LiftTransformation())->transformResult($value); + + $this->assertInstanceOf(OK::class, $actual); + $this->assertInstanceOf(Effect::class, $actual->value()); + $this->assertEquals($value, $actual->value()->value()); + } + + public function testApplyToOk() : void + { + $value = ['im in an array']; + $result = (new LiftTransformation())->applyTo(new OK($value)); + $this->assertInstanceOf(OK::class, $result); + $this->assertInstanceOf(Effect::class, $result->value()); + $this->assertEquals($value, $result->value()->value()); + } + + public function testApplyToError() : void + { + $error = new Error('some error'); + $result = (new LiftTransformation())->applyTo($error); + $this->assertEquals($error, $result); + } +} diff --git a/tests/Refinery/FactoryTest.php b/tests/Refinery/FactoryTest.php index 8fe26ae6487c..bb9187d91090 100644 --- a/tests/Refinery/FactoryTest.php +++ b/tests/Refinery/FactoryTest.php @@ -97,7 +97,7 @@ public function testCreateDateTimeGroup() $this->assertInstanceOf(\ILIAS\Refinery\DateTime\Group::class, $group); } - public function testCreateUriGrouo() + public function testCreateUriGroup() { $group = $this->basicFactory->uri(); $this->assertInstanceOf(\ILIAS\Refinery\URI\Group::class, $group); @@ -111,4 +111,10 @@ public function testByTryingInGroup() ]); $this->assertInstanceOf(\ILIAS\Refinery\ByTrying::class, $instance); } + + public function testRandomGroup() + { + $instance = $this->basicFactory->random(); + $this->assertInstanceOf(\ILIAS\Refinery\Random\Group::class, $instance); + } } diff --git a/tests/Refinery/Random/Effect/ShuffleEffect.php b/tests/Refinery/Random/Effect/ShuffleEffect.php new file mode 100644 index 000000000000..27ca6ff540d4 --- /dev/null +++ b/tests/Refinery/Random/Effect/ShuffleEffect.php @@ -0,0 +1,21 @@ + + */ +namespace ILIAS\Tests\Refinery\Random\Effect; + +use PHPUnit\Framework\TestCase; +use ILIAS\Refinery\Random\Seed\Seed; + +class ShuffleEffect extends TestCase +{ + public function testValue() : void + { + $value = ['arrr'] + $seedMock = $this->getMockBuilder(Seed::class)->getMock(); + $seedMock->expects(self::once())->method('seedRandomGenerator'); + $result = (new ShuffleEffect($value, $seedMock))->value(); + $this->assertEquals($value, $result); + } +} diff --git a/tests/Refinery/Random/GroupTest.php b/tests/Refinery/Random/GroupTest.php new file mode 100644 index 000000000000..49e956d60f15 --- /dev/null +++ b/tests/Refinery/Random/GroupTest.php @@ -0,0 +1,40 @@ + + */ +namespace ILIAS\Tests\Refinery\Random; + +use ILIAS\Refinery\Random\Group; +use ILIAS\Refinery\Transformation; +use PHPUnit\Framework\TestCase; +use ILIAS\Refinery\Random\Seed\Seed; +use ILIAS\Refinery\Effect\Transformation\LiftTransformation; +use ILIAS\Refinery\Random\Transformation\ShuffleTransformation; + +class GroupTest extends TestCase +{ + /** + * @var Group + */ + private $group; + + public function setUp() : void + { + $this->group = new Group(); + } + + public function testShuffle() : void + { + $mock = $this->getMockBuilder(Seed::class)->getMock(); + $mock->expects(self::never())->method('seedRandomGenerator'); + $instance = $this->group->shuffleArray($mock); + $this->assertInstanceOf(ShuffleTransformation::class, $instance); + } + + public function testDontShuffle() : void + { + $instance = $this->group->dontShuffle(); + $this->assertInstanceOf(LiftTransformation::class, $instance); + } +} diff --git a/tests/Refinery/Random/Transformation/ShuffleTransformationTest.php b/tests/Refinery/Random/Transformation/ShuffleTransformationTest.php new file mode 100644 index 000000000000..3fe8e174c083 --- /dev/null +++ b/tests/Refinery/Random/Transformation/ShuffleTransformationTest.php @@ -0,0 +1,37 @@ + + */ +namespace ILIAS\Tests\Refinery\Random\Transformation; + +use ILIAS\Refinery\Random\Transformation\ShuffleTransformation; +use ILIAS\Refinery\Random\Seed\Seed; +use ILIAS\Refinery\Random\Effect\ShuffleEffect; +use ILIAS\Data\NotOKException; +use ILIAS\Data\Result\OK; +use ILIAS\Data\Result\Error; +use PHPUnit\Framework\TestCase; + +class ShuffleTransformationTest extends TestCase +{ + public function testTransformResultSuccess() : void + { + $value = ['nrrrrg']; + $seedMock = $this->getMockBuilder(Seed::class)->getMock(); + $seedMock->expects(self::never())->method('seedRandomGenerator'); + + $result = (new ShuffleTransformation($seedMock))->transformResult($value); + $this->assertInstanceOf(OK::class, $result); + $this->assertInstanceOf(ShuffleEffect::class, $result->value()); + } + + public function testTransformResultFailure() : void + { + $seedMock = $this->getMockBuilder(Seed::class)->getMock(); + $seedMock->expects(self::never())->method('seedRandomGenerator'); + + $result = (new ShuffleTransformation($seedMock))->transformResult('im no array'); + $this->assertInstanceOf(Error::class, $result); + } +} From 0f61ceb15c2ae7f6f69532732b2a22fade3d283c Mon Sep 17 00:00:00 2001 From: Lukas Scharmer Date: Fri, 5 Nov 2021 10:51:51 +0100 Subject: [PATCH 2/2] Adjust Seed and LiftTransformation --- .../Transformation/LiftTransformation.php | 22 ++++++++------- src/Refinery/Random/Seed/GivenSeed.php | 5 ---- src/Refinery/Random/Seed/Seed.php | 1 - .../Transformation/LiftTransformationTest.php | 27 +++---------------- .../ShuffleTransformationTest.php | 11 ++++---- 5 files changed, 20 insertions(+), 46 deletions(-) diff --git a/src/Refinery/Effect/Transformation/LiftTransformation.php b/src/Refinery/Effect/Transformation/LiftTransformation.php index 304e85be2464..887964c096e3 100644 --- a/src/Refinery/Effect/Transformation/LiftTransformation.php +++ b/src/Refinery/Effect/Transformation/LiftTransformation.php @@ -7,15 +7,23 @@ use ILIAS\Data\Result; use ILIAS\Data\Result\Ok; use ILIAS\Refinery\Effect\Effect; +use ILIAS\Refinery\DeriveInvokeFromTransform; +/** + * "Lifts" a value to an Effect (T => Effect). + * Creates a parameterized type (in this case Effect) from another type (T). + * @see https://wiki.haskell.org/Lifting + */ class LiftTransformation implements Transformation { + use DeriveInvokeFromTransform; + /** * Same as transform but returns a Result instead of an exception. * * @return Result */ - public function transformResult($from) : Result + private function transformResult($from) : Result { return $this->validate($from)->map([$this, 'createEffect']); } @@ -34,15 +42,9 @@ public function transform($from) */ public function applyTo(Result $result) : Result { - return $result->then([$this, 'transformResult']); - } - - /** - * @return Result - */ - public function __invoke($from) - { - return $this->transform($from); + return $result->then(function ($from) { + return $this->transformResult($from); + }); } /** diff --git a/src/Refinery/Random/Seed/GivenSeed.php b/src/Refinery/Random/Seed/GivenSeed.php index c2f8f5530067..b541a6e162de 100644 --- a/src/Refinery/Random/Seed/GivenSeed.php +++ b/src/Refinery/Random/Seed/GivenSeed.php @@ -18,9 +18,4 @@ public function seedRandomGenerator() : void { mt_srand($this->seed); } - - public function getSeed() : int - { - return $this->seed(); - } } diff --git a/src/Refinery/Random/Seed/Seed.php b/src/Refinery/Random/Seed/Seed.php index 7dbe51d12ffa..d36c17a23dd6 100644 --- a/src/Refinery/Random/Seed/Seed.php +++ b/src/Refinery/Random/Seed/Seed.php @@ -8,5 +8,4 @@ interface Seed { public function seedRandomGenerator() : void; - public function getSeed() : int; } diff --git a/tests/Refinery/Effect/Transformation/LiftTransformationTest.php b/tests/Refinery/Effect/Transformation/LiftTransformationTest.php index 1482cdac4354..235c8bcd30b9 100644 --- a/tests/Refinery/Effect/Transformation/LiftTransformationTest.php +++ b/tests/Refinery/Effect/Transformation/LiftTransformationTest.php @@ -6,7 +6,7 @@ namespace ILIAS\Tests\Refinery\Effect\Transformation; use ILIAS\Data\NotOKException; -use ILIAS\Data\Result\OK; +use ILIAS\Data\Result\Ok; use ILIAS\Data\Result\Error; use ILIAS\Refinery\Effect\Transformation\LiftTransformation; use PHPUnit\Framework\TestCase; @@ -24,32 +24,11 @@ public function testTransform() : void $this->assertEquals($value, $actual->value()); } - public function testInvoke() : void - { - $value = ['bababa', 'uauauau']; - - $actual = (new LiftTransformation())($value); - - $this->assertInstanceOf(Effect::class, $actual); - $this->assertEquals($value, $actual->value()); - } - - public function testTransformResult() : void - { - $value = 678; - - $actual = (new LiftTransformation())->transformResult($value); - - $this->assertInstanceOf(OK::class, $actual); - $this->assertInstanceOf(Effect::class, $actual->value()); - $this->assertEquals($value, $actual->value()->value()); - } - public function testApplyToOk() : void { $value = ['im in an array']; - $result = (new LiftTransformation())->applyTo(new OK($value)); - $this->assertInstanceOf(OK::class, $result); + $result = (new LiftTransformation())->applyTo(new Ok($value)); + $this->assertInstanceOf(Ok::class, $result); $this->assertInstanceOf(Effect::class, $result->value()); $this->assertEquals($value, $result->value()->value()); } diff --git a/tests/Refinery/Random/Transformation/ShuffleTransformationTest.php b/tests/Refinery/Random/Transformation/ShuffleTransformationTest.php index 3fe8e174c083..f936cc6b57fd 100644 --- a/tests/Refinery/Random/Transformation/ShuffleTransformationTest.php +++ b/tests/Refinery/Random/Transformation/ShuffleTransformationTest.php @@ -9,7 +9,7 @@ use ILIAS\Refinery\Random\Seed\Seed; use ILIAS\Refinery\Random\Effect\ShuffleEffect; use ILIAS\Data\NotOKException; -use ILIAS\Data\Result\OK; +use ILIAS\Data\Result\Ok; use ILIAS\Data\Result\Error; use PHPUnit\Framework\TestCase; @@ -21,17 +21,16 @@ public function testTransformResultSuccess() : void $seedMock = $this->getMockBuilder(Seed::class)->getMock(); $seedMock->expects(self::never())->method('seedRandomGenerator'); - $result = (new ShuffleTransformation($seedMock))->transformResult($value); - $this->assertInstanceOf(OK::class, $result); - $this->assertInstanceOf(ShuffleEffect::class, $result->value()); + $result = (new ShuffleTransformation($seedMock))->transform($value); + $this->assertInstanceOf(ShuffleEffect::class, $result); } public function testTransformResultFailure() : void { + $this->expectException(NotOKException::class); $seedMock = $this->getMockBuilder(Seed::class)->getMock(); $seedMock->expects(self::never())->method('seedRandomGenerator'); - $result = (new ShuffleTransformation($seedMock))->transformResult('im no array'); - $this->assertInstanceOf(Error::class, $result); + $result = (new ShuffleTransformation($seedMock))->transform('im no array'); } }