Skip to content

Commit e6e0027

Browse files
authored
add multiFilter utility method to DataTable classes so it is easier to iterate over multiple DataTables concurrently (matomo-org#19902)
1 parent 3ed1534 commit e6e0027

5 files changed

Lines changed: 172 additions & 0 deletions

File tree

core/DataTable.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,22 @@ public function filter($className, $parameters = array())
532532
$filter->filter($this);
533533
}
534534

535+
/**
536+
* Invokes `$filter` with this table and every table in `$otherTables`. The result of `$filter()` is returned.
537+
*
538+
* This method is used to iterate over multiple DataTable\Map's concurrently.
539+
*
540+
* See {@link \Piwik\DataTable\Map::multiFilter()} for more information.
541+
*
542+
* @param DataTable[] $otherTables
543+
* @param callable filter A function like `function (DataTable $thisTable, $otherTable1, $otherTable2) {}`.
544+
* @return mixed The result of $filter.
545+
*/
546+
public function multiFilter($otherTables, $filter)
547+
{
548+
return $filter(...array_merge([$this], $otherTables));
549+
}
550+
535551
/**
536552
* Applies a filter to all subtables but not to this datatable.
537553
*

core/DataTable/DataTableInterface.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public function getRowsCount();
1818
public function queueFilter($className, $parameters = array());
1919
public function applyQueuedFilters();
2020
public function filter($className, $parameters = array());
21+
public function multiFilter($otherTables, $filter);
2122
public function getFirstRow();
2223
public function __toString();
2324
public function enableRecursiveSort();

core/DataTable/Map.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,35 @@ public function filter($className, $parameters = array())
110110
}
111111
}
112112

113+
/**
114+
* Apply a callback to all tables contained by this instance and tables with the same key in $otherTables.
115+
*
116+
* This method is used to iterate over multiple DataTable\Map's concurrently.
117+
*
118+
* $filter will be called with multiple DataTable instances, the first is the instance contained in
119+
* this Map instance. The rest are the corresponding instances found in $otherTables. The position of
120+
* the parameter in $filter corresponds with the position in $otherTables.
121+
*
122+
* If a key exists in this instance but not in one of the otherTables, $filter will be invoked with null
123+
* for that parameter.
124+
*
125+
* @param Map[] $otherTables Other tables to invoke $filter with.
126+
* @param callable $filter A function like `function (DataTable $thisTable, $otherTable1, $otherTable2, ...) {}`.
127+
* @return mixed[] The return value of each `multiFilter()` call made on child tables, indexed by the keys in this Map instance.
128+
*/
129+
public function multiFilter($otherTables, $filter)
130+
{
131+
$result = [];
132+
foreach ($this->getDataTables() as $key => $childTable) {
133+
$otherChildTables = array_map(function ($otherTable) use ($key) {
134+
return !empty($otherTable) && $otherTable->hasTable($key) ? $otherTable->getTable($key) : null;
135+
}, $otherTables);
136+
137+
$result[$key] = $childTable->multiFilter($otherChildTables, $filter);
138+
}
139+
return $result;
140+
}
141+
113142
/**
114143
* Apply a filter to all subtables contained by this instance.
115144
*

tests/PHPUnit/Unit/DataTable/MapTest.php

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,93 @@ public function setUp(): void
1717
Manager::getInstance()->deleteAll();
1818
}
1919

20+
public function testMultiFilter()
21+
{
22+
// first map
23+
$innerTable1 = new DataTable();
24+
$innerTable1->addRowsFromSimpleArray([
25+
['label' => '2020-03-04.1', 'value' => 1],
26+
]);
27+
$innerTable2 = new DataTable();
28+
$innerTable2->addRowsFromSimpleArray([
29+
['label' => '2020-03-04.2', 'value' => 3],
30+
]);
31+
$innerTable3 = new DataTable();
32+
$innerTable3->addRowsFromSimpleArray([
33+
['label' => '2020-05-05.3', 'value' => 5],
34+
]);
35+
$innerTable4 = new DataTable();
36+
$innerTable4->addRowsFromSimpleArray([
37+
['label' => '2020-05-05.4', 'value' => 7],
38+
]);
39+
40+
$innerMap1 = new DataTable\Map();
41+
$innerMap1->addTable($innerTable1, '1');
42+
$innerMap1->addTable($innerTable2, '2');
43+
44+
$innerMap2 = new DataTable\Map();
45+
$innerMap2->addTable($innerTable3, '3');
46+
$innerMap2->addTable($innerTable4, '4');
47+
48+
$outerMap1 = new DataTable\Map();
49+
$outerMap1->addTable($innerMap1, '2020-03-04');
50+
$outerMap1->addTable($innerMap2, '2020-05-05');
51+
52+
// second map
53+
$innerTable5 = new DataTable();
54+
$innerTable5->addRowsFromSimpleArray([
55+
['label' => '2020-03-04.1', 'value' => 9],
56+
]);
57+
$innerTable6 = new DataTable();
58+
$innerTable6->addRowsFromSimpleArray([
59+
['label' => '2020-03-04.2', 'value' => 11],
60+
]);
61+
$innerTable7 = new DataTable();
62+
$innerTable7->addRowsFromSimpleArray([
63+
['label' => '2020-05-06.5', 'value' => 13],
64+
]);
65+
$innerTable8 = new DataTable();
66+
$innerTable8->addRowsFromSimpleArray([
67+
['label' => '2020-05-06.4', 'value' => 15],
68+
]);
69+
70+
$innerMap3 = new DataTable\Map();
71+
$innerMap3->addTable($innerTable5, '1');
72+
$innerMap3->addTable($innerTable6, '2');
73+
74+
$innerMap4 = new DataTable\Map();
75+
$innerMap4->addTable($innerTable7, '5');
76+
$innerMap4->addTable($innerTable8, '4');
77+
78+
$outerMap2 = new DataTable\Map();
79+
$outerMap2->addTable($innerMap3, '2020-03-04');
80+
$outerMap2->addTable($innerMap4, '2020-05-06');
81+
82+
$visitedLabels = [];
83+
$result = $outerMap1->multiFilter([$outerMap2], function ($table1, $table2) use (&$visitedLabels) {
84+
$label1 = $table1->getFirstRow()->getColumn('label');
85+
$value1 = $table1->getFirstRow()->getColumn('value');
86+
87+
$label2 = empty($table2) ? false : $table2->getFirstRow()->getColumn('label');
88+
$value2 = empty($table2) ? 0 : $table2->getFirstRow()->getColumn('value');
89+
90+
$visitedLabels[] = [$label1, $label2];
91+
92+
return $value1 + $value2;
93+
});
94+
95+
$this->assertEquals([
96+
'2020-03-04' => ['1' => 10, '2' => 14],
97+
'2020-05-05' => ['3' => 5, '4' => 7],
98+
], $result);
99+
$this->assertEquals([
100+
['2020-03-04.1', '2020-03-04.1'],
101+
['2020-03-04.2', '2020-03-04.2'],
102+
['2020-05-05.3', false],
103+
['2020-05-05.4', false],
104+
], $visitedLabels);
105+
}
106+
20107
private function createTestDataTable()
21108
{
22109
$result = new DataTable();

tests/PHPUnit/Unit/DataTableTest.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,45 @@ protected function _getSimpleTestDataTable()
4747
return $table;
4848
}
4949

50+
protected function _getSimpleTestDataTable2()
51+
{
52+
$table = new DataTable;
53+
$table->addRowsFromArray(
54+
array(
55+
array(Row::COLUMNS => array('label' => 'ten', 'count' => 10)),
56+
array(Row::COLUMNS => array('label' => 'ninety', 'count' => 20)),
57+
array(Row::COLUMNS => array('label' => 'hundred', 'count' => 30)),
58+
DataTable::ID_SUMMARY_ROW => array(Row::COLUMNS => array('label' => 'summary', 'count' => 60))
59+
)
60+
);
61+
$table->setTotalsRow(new Row(array(Row::COLUMNS => array('label' => 'Total', 'count' => 60))));
62+
return $table;
63+
}
64+
65+
public function testMultiFilter()
66+
{
67+
$table = $this->_getSimpleTestDataTable();
68+
$table2 = $this->_getSimpleTestDataTable2();
69+
70+
$result = $table->multiFilter([$table2], function ($thisTable, $otherTable) {
71+
$thisTable->addDataTable($otherTable);
72+
return 5;
73+
});
74+
75+
$tableExpected = new DataTable();
76+
$tableExpected->addRowsFromArray(
77+
[
78+
array(Row::COLUMNS => array('label' => 'ten', 'count' => 20)),
79+
array(Row::COLUMNS => array('label' => 'ninety', 'count' => 110)),
80+
array(Row::COLUMNS => array('label' => 'hundred', 'count' => 130)),
81+
DataTable::ID_SUMMARY_ROW => array(Row::COLUMNS => array('label' => 'summary', 'count' => 260))
82+
]
83+
);
84+
85+
$this->assertEquals(5, $result);
86+
$this->assertEquals($tableExpected->getRows(), $table->getRows());
87+
}
88+
5089
public function testRenameColumn()
5190
{
5291
$table = $this->_getSimpleTestDataTable();

0 commit comments

Comments
 (0)