Skip to content

Commit

Permalink
[TASK] Introduce PermutationUtility
Browse files Browse the repository at this point in the history
Permutations are currently used in test cases - however it would
be possible to make use of this functionality in regular sources
as well. That's why corresponding methods are moved into a new
PermutationUtility implementation.

Resolves: #90055
Releases: master, 9.5
Change-Id: I05d978ada2021c7db1b7abeb8a21a825472ae8cc
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/62819
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Benni Mack <benni@typo3.org>
  • Loading branch information
ohader authored and bmack committed Jan 6, 2020
1 parent 40fd85f commit 61a1260
Show file tree
Hide file tree
Showing 4 changed files with 346 additions and 17 deletions.
108 changes: 108 additions & 0 deletions typo3/sysext/core/Classes/Utility/PermutationUtility.php
@@ -0,0 +1,108 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Core\Utility;

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

/**
* Class with helper functions for permuting items.
*/
class PermutationUtility
{
/**
* Combines string items of multiple arrays as cross-product into flat items.
*
* Example:
* + meltStringItems([['a', 'b'], ['c', 'd'], ['e', 'f']])
* + results into ['ace', 'acf', 'ade', 'adf', 'bce', 'bcf', 'bde', 'bdf']
*
* @param array[] $payload Distinct array that should be melted
* @param string $previousResult Previous item results
* @return array
*/
public static function meltStringItems(array $payload, string $previousResult = ''): array
{
$results = [];
$items = static::nextItems($payload);
foreach ($items as $item) {
$resultItem = $previousResult . static::asString($item);
if (!empty($payload)) {
$results = array_merge(
$results,
static::meltStringItems($payload, $resultItem)
);
continue;
}
$results[] = $resultItem;
}
return $results;
}

/**
* Combines arbitrary items of multiple arrays as cross-product into flat items.
*
* Example:
* + meltArrayItems(['a','b'], ['c','e'], ['f','g'])
* + results into ['a', 'c', 'e'], ['a', 'c', 'f'], ['a', 'd', 'e'], ['a', 'd', 'f'],
* ['b', 'c', 'e'], ['b', 'c', 'f'], ['b', 'd', 'e'], ['b', 'd', 'f'],
*
* @param array[] $payload Distinct items that should be melted
* @param array $previousResult Previous item results
* @return array
*/
public static function meltArrayItems(array $payload, array $previousResult = []): array
{
$results = [];
$items = static::nextItems($payload);
foreach ($items as $item) {
$resultItems = $previousResult;
$resultItems[] = $item;
if (!empty($payload)) {
$results = array_merge(
$results,
static::meltArrayItems($payload, $resultItems)
);
continue;
}
$results[] = $resultItems;
}
return $results;
}

protected static function nextItems(array &$payload): iterable
{
$items = array_shift($payload);
if (is_iterable($items)) {
return $items;
}
throw new \LogicException(
sprintf('Expected iterable, got %s', gettype($items)),
1578164101
);
}

protected static function asString($item): string
{
if (is_string($item)) {
return $item;
}
if (is_object($item) && method_exists($item, '__toString')) {
return (string)$item;
}
throw new \LogicException(
sprintf('Expected string, got %s', gettype($item)),
1578164102
);
}
}
34 changes: 34 additions & 0 deletions typo3/sysext/core/Tests/Unit/Utility/Fixtures/StringValue.php
@@ -0,0 +1,34 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Core\Tests\Unit\Utility\Fixtures;

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

class StringValue
{
/**
* @var string
*/
private $value;

public function __construct(string $value)
{
$this->value = $value;
}

public function __toString(): string
{
return $this->value;
}
}
202 changes: 202 additions & 0 deletions typo3/sysext/core/Tests/Unit/Utility/PermutationUtilityTest.php
@@ -0,0 +1,202 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Core\Tests\Unit\Utility;

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

use TYPO3\CMS\Core\Tests\Unit\Utility\Fixtures\StringValue;
use TYPO3\CMS\Core\Utility\PermutationUtility;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;

/**
* Test case for class \TYPO3\CMS\Core\Utility\PermutationUtility
*/
class PermutationUtilityTest extends UnitTestCase
{
public function meltStringItemsDataProvider(): array
{
return [
'string items' => [
[
['a', 'b'],
['c', 'd'],
['e', 'f'],
],
['ace', 'acf', 'ade', 'adf', 'bce', 'bcf', 'bde', 'bdf'],
],
'string & empty value items' => [
[
['a', ''],
['b', ''],
['c', ''],
],
['abc', 'ab', 'ac', 'a', 'bc', 'b', 'c', '']
],
'object::__toString() items' => [
[
[new StringValue('a'), new StringValue('b')],
[new StringValue('c'), new StringValue('d')],
[new StringValue('e'), new StringValue('f')],
],
['ace', 'acf', 'ade', 'adf', 'bce', 'bcf', 'bde', 'bdf'],
],
'string & object::__toString() items' => [
[
['a', new StringValue('b')],
['c', new StringValue('d')],
['e', new StringValue('f')],
],
['ace', 'acf', 'ade', 'adf', 'bce', 'bcf', 'bde', 'bdf'],
],
'string items with invalid object' => [
[
['a', 'b'],
['c', 'd'],
['e', 'f', new \stdClass()],
],
1578164102,
],
'string items with invalid integer' => [
[
['a', 'b'],
['c', 'd'],
['e', 'f', 123],
],
1578164102,
],
'string items with invalid boolean' => [
[
['a', 'b'],
['c', 'd'],
['e', 'f', true],
],
1578164102,
],
'string items with invalid array' => [
[
['a', 'b'],
['c', 'd'],
['e', 'f', []],
],
1578164102,
],
'string items with invalid invocation' => [
[
'a', 'b',
],
1578164101,
],
];
}

/**
* @param array $payload
* @param array|int $expectation
*
* @test
* @dataProvider meltStringItemsDataProvider
*/
public function meltStringItemsIsExecuted(array $payload, $expectation): void
{
if (is_int($expectation)) {
$this->expectException(\LogicException::class);
$this->expectExceptionCode($expectation);
}
self::assertSame($expectation, PermutationUtility::meltStringItems($payload));
}

public function meltArrayItemsDataProvider(): array
{
$aStringValue = new StringValue('a');
$bStringValue = new StringValue('b');
$cStringValue = new StringValue('c');
$dStringValue = new StringValue('d');

return [
'string items' => [
[
['a', 'b'],
['c', 'd'],
['e', 'f'],
],
[
['a', 'c', 'e'], ['a', 'c', 'f'], ['a', 'd', 'e'], ['a', 'd', 'f'],
['b', 'c', 'e'], ['b', 'c', 'f'], ['b', 'd', 'e'], ['b', 'd', 'f'],
],
],
'object::__toString() items' => [
[
[$aStringValue, $bStringValue],
[$cStringValue, $dStringValue],
],
[
[$aStringValue, $cStringValue], [$aStringValue, $dStringValue],
[$bStringValue, $cStringValue], [$bStringValue, $dStringValue],
],
],
'mixed items' => [
[
[$aStringValue, 'a', 1],
[$bStringValue, 'b', 2],
],
[
[$aStringValue, $bStringValue], [$aStringValue, 'b'], [$aStringValue, 2],
['a', $bStringValue], ['a', 'b'], ['a', 2],
[1, $bStringValue], [1, 'b'], [1, 2],
],
],
'string items in ArrayObject' => [
[
new \ArrayObject(['a', 'b']),
new \ArrayObject(['c', 'd']),
new \ArrayObject(['e', 'f']),
],
[
['a', 'c', 'e'], ['a', 'c', 'f'], ['a', 'd', 'e'], ['a', 'd', 'f'],
['b', 'c', 'e'], ['b', 'c', 'f'], ['b', 'd', 'e'], ['b', 'd', 'f'],
],
],
'string items with invalid invocation' => [
[
'a', 'b',
],
1578164101,
],
'object::__toString() items with invalid invocation' => [
[
new StringValue('b'),
new StringValue('c'),
new StringValue('d'),
],
1578164101,
],
];
}

/**
* @param array $payload
* @param array|int $expectation
*
* @test
* @dataProvider meltArrayItemsDataProvider
*/
public function meltArrayItemsIsExecuted(array $payload, $expectation): void
{
if (is_int($expectation)) {
$this->expectException(\LogicException::class);
$this->expectExceptionCode($expectation);
}
self::assertSame($expectation, PermutationUtility::meltArrayItems($payload));
}
}
Expand Up @@ -69,26 +69,11 @@ abstract class AbstractTestCase extends FunctionalTestCase
* @param callable $finalCallback Callback being executed on last multiplier
* @param string $prefix Prefix containing concatenated previous values
* @return array
* @todo Replace all calls with PermutationUtility::meltStringItems()
*/
protected function meltStrings(array $arrays, callable $finalCallback = null, string $prefix = ''): array
{
$results = [];
$array = array_shift($arrays);
foreach ($array as $item) {
$resultItem = $prefix . $item;
if (count($arrays) > 0) {
$results = array_merge(
$results,
$this->meltStrings($arrays, $finalCallback, $resultItem)
);
continue;
}
if ($finalCallback !== null) {
$resultItem = call_user_func($finalCallback, $resultItem);
}
$results[] = $resultItem;
}
return $results;
return \TYPO3\CMS\Core\Utility\PermutationUtility::meltStringItems($arrays);
}

/**
Expand Down

0 comments on commit 61a1260

Please sign in to comment.