Skip to content

Commit 34427b9

Browse files
Hannes Giesenowhgiesenow
authored andcommitted
Added matrix field helper
1 parent c3c0cba commit 34427b9

File tree

9 files changed

+308
-3
lines changed

9 files changed

+308
-3
lines changed

config/services.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ services:
3838
# arguments:
3939
# $tagsService: '@eztags.api.service.tags'
4040

41+
elbformat_field_helper.field_helper.matrix:
42+
class: Elbformat\FieldHelperBundle\FieldHelper\MatrixFieldHelper
43+
tags: ['elbformat_field_helper.field_helper']
44+
4145
elbformat_field_helper.field_helper.number:
4246
class: Elbformat\FieldHelperBundle\FieldHelper\NumberFieldHelper
4347
tags: ['elbformat_field_helper.field_helper']

docs/changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## v1.2.2
4+
Added field helper for
5+
* Matrix
6+
37
## v1.2.1
48
Added forgotten field helper for
59
* Selection

docs/fields.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Implemented types are:
2222
| ISBN | | |
2323
| Keyword | | |
2424
| MapLocation | | |
25-
| Matrix | ezmatrix | |
25+
| Matrix | ezmatrix | MatrixFieldHelper |
2626
| Media | | |
2727
| Null | | |
2828
| Page | | |

docs/testing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ vendor/bin/psalm
1313
To run integration tests, you need to spin up a database first. To ease this, you can use the docker-compose setup provided
1414
```bash
1515
docker-compose up -d
16-
docker-compose run php bash
16+
docker-compose exec php sh
1717
vendor/bin/phpunit --testsuite integration
1818
```
1919

src/FieldHelper/MatrixFieldHelper.php

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Elbformat\FieldHelperBundle\FieldHelper;
6+
7+
use Elbformat\FieldHelperBundle\Exception\FieldNotFoundException;
8+
use Elbformat\FieldHelperBundle\Exception\InvalidFieldTypeException;
9+
use eZ\Publish\API\Repository\Values\Content\Content;
10+
use eZ\Publish\API\Repository\Values\Content\ContentStruct;
11+
use EzSystems\EzPlatformMatrixFieldtype\FieldType\Value;
12+
13+
/**
14+
* @author Hannes Giesenow <hannes.giesenow@elbformat.de>
15+
*/
16+
class MatrixFieldHelper extends AbstractFieldHelper
17+
{
18+
/**
19+
* Return a row-based 2-dimensional array.
20+
* [
21+
* [A1,B1,C1],
22+
* [A2,B2,C2],
23+
* ]
24+
*
25+
* @return string[][]
26+
*
27+
* @throws FieldNotFoundException
28+
* @throws InvalidFieldTypeException
29+
*/
30+
public function getArray(Content $content, string $fieldName): array
31+
{
32+
$rows = $this->getValue($content, $fieldName)->getRows();
33+
$result = [];
34+
/** @var Value\Row $row */
35+
foreach ($rows as $row) {
36+
/** @var string[] $cells */
37+
$cells = $row->getCells();
38+
$result[] = array_values($cells);
39+
}
40+
41+
return $result;
42+
}
43+
44+
/**
45+
* Return a key-value based array structure of the table. Each row has the headlines as key.
46+
* [
47+
* [A1 => A2, B1 => B2, C1 => C2],
48+
* [A1 => A3, B1 => B3, C1 => C3],
49+
* ]
50+
*
51+
* @return mixed[][]
52+
*
53+
* @throws FieldNotFoundException
54+
* @throws InvalidFieldTypeException
55+
*/
56+
public function getAssoc(Content $content, string $fieldName): array
57+
{
58+
$rows = $this->getArray($content, $fieldName);
59+
$headlines = $this->getHeadlineIds($content, $fieldName);
60+
$numHeadlines = count($headlines);
61+
62+
$result = [];
63+
/** @var Value\Row $row */
64+
foreach ($rows as $cells) {
65+
$rowData = [];
66+
for ($i = 0; $i < $numHeadlines; $i++) {
67+
$rowData[$headlines[$i]] = $cells[$i];
68+
}
69+
$result[] = $rowData;
70+
}
71+
72+
return $result;
73+
}
74+
75+
/**
76+
* Return a key-value list for 2-column tables.
77+
* [A1 => B1, A2 => B2, A3 => B3]
78+
*
79+
* @return mixed[]
80+
*/
81+
public function getKeyValue(Content $content, string $fieldName, ?string $key = null, ?string $val = null): array
82+
{
83+
$rows = $this->getValue($content, $fieldName)->getRows();
84+
if (null === $key) {
85+
$key = $this->getHeadlineIds($content, $fieldName)[0] ?? '';
86+
}
87+
if (null === $val) {
88+
$val = $this->getHeadlineIds($content, $fieldName)[1] ?? '';
89+
}
90+
91+
$result = [];
92+
/** @var Value\Row $row */
93+
foreach ($rows as $row) {
94+
/** @var string[] $cells */
95+
$cells = $row->getCells();
96+
$result[$cells[$key]] = $cells[$val] ?? '';
97+
}
98+
99+
return $result;
100+
}
101+
102+
/**
103+
* Return a list for 1-column tables.
104+
* [A1,A2,A3]
105+
*
106+
* @return string[]
107+
*/
108+
public function getList(Content $content, string $fieldName, string $columnName = null): array
109+
{
110+
if (null === $columnName) {
111+
$columnName = $this->getHeadlineIds($content, $fieldName)[0] ?? '';
112+
}
113+
$rows = $this->getValue($content, $fieldName)->getRows();
114+
$result = [];
115+
/** @var Value\Row $row */
116+
foreach ($rows as $row) {
117+
$cells = $row->getCells();
118+
$result[] = (string) $cells[$columnName];
119+
}
120+
121+
return $result;
122+
}
123+
124+
public function isEmpty(Content $content, string $fieldName): bool
125+
{
126+
return $this->getValue($content, $fieldName)->getRows()->count() <= 0;
127+
}
128+
129+
/**
130+
* @param array<array<string,string>> $rowsWithCols
131+
*/
132+
public function updateAssoc(ContentStruct $struct, string $fieldName, array $rowsWithCols, ?Content $content = null): bool
133+
{
134+
// No changes
135+
if (null !== $content) {
136+
$current = $this->getArray($content, $fieldName);
137+
if ($this->arrayEquals($current, $rowsWithCols)) {
138+
return false;
139+
}
140+
}
141+
142+
// Convert to rows
143+
$rows = [];
144+
foreach ($rowsWithCols as $cols) {
145+
$rows[] = new Value\Row($cols);
146+
}
147+
148+
$struct->setField($fieldName, $rows);
149+
150+
return true;
151+
}
152+
153+
/**
154+
* @param string[] $values
155+
*/
156+
public function updateList(ContentStruct $struct, string $fieldName, array $values, string $columnName, ?Content $content = null): bool
157+
{
158+
// No changes
159+
if (null !== $content) {
160+
$field = $this->getList($content, $fieldName, $columnName);
161+
if ($this->isListEqual($field, $values)) {
162+
return false;
163+
}
164+
}
165+
// Convert to rows
166+
$rows = [];
167+
foreach ($values as $value) {
168+
$rows[] = new Value\Row([$columnName => $value]);
169+
}
170+
171+
$struct->setField($fieldName, $rows);
172+
173+
return true;
174+
}
175+
176+
protected function getValue(Content $content, string $fieldName): Value
177+
{
178+
$field = $this->getField($content, $fieldName);
179+
if (!$field->value instanceof Value) {
180+
throw InvalidFieldTypeException::fromActualAndExpected($field->value, [Value::class]);
181+
}
182+
183+
return $field->value;
184+
}
185+
186+
/**
187+
* @param string[] $list1
188+
* @param string[] $list2
189+
*/
190+
protected function isListEqual(array $list1, array $list2): bool
191+
{
192+
if (\count($list1) !== \count($list2)) {
193+
return false;
194+
}
195+
for ($i = 0, $iMax = \count($list1); $i < $iMax; ++$i) {
196+
if ($list1[$i] !== $list2[$i]) {
197+
return false;
198+
}
199+
}
200+
201+
return true;
202+
}
203+
204+
/** @return string[] */
205+
protected function getHeadlineIds(Content $content, string $fieldName): array
206+
{
207+
$fieldDef = $content->getContentType()->getFieldDefinition($fieldName);
208+
if (null === $fieldDef) {
209+
return [];
210+
}
211+
212+
$colIds = [];
213+
/** @var array[] $columns */
214+
$columns = $fieldDef->fieldSettings['columns'];
215+
foreach ($columns as $colDef) {
216+
$colIds[] = (string) $colDef['identifier'];
217+
}
218+
219+
return $colIds;
220+
}
221+
222+
protected function arrayEquals(array $arr1, array $arr2): bool
223+
{
224+
return $this->arrayContains($arr1, $arr2) && $this->arrayContains($arr2, $arr1);
225+
}
226+
227+
protected function arrayContains(array $arr1, array $arr2): bool
228+
{
229+
return 1 > count(
230+
array_udiff($arr1, $arr2, function ($v1, $v2): int {
231+
if (is_array($v1) && is_array($v2)) {
232+
return !$this->arrayContains($v1, $v2) ? 1 : 0;
233+
}
234+
if (is_array($v1) || is_array($v2)) {
235+
return 1;
236+
}
237+
238+
return $v1 !== $v2 ? 1 : 0;
239+
})
240+
);
241+
}
242+
}

src/Registry/Registry.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Elbformat\FieldHelperBundle\FieldHelper\FieldHelperInterface;
1313
use Elbformat\FieldHelperBundle\FieldHelper\FileFieldHelper;
1414
use Elbformat\FieldHelperBundle\FieldHelper\ImageFieldHelper;
15+
use Elbformat\FieldHelperBundle\FieldHelper\MatrixFieldHelper;
1516
use Elbformat\FieldHelperBundle\FieldHelper\NetgenTagsFieldHelper;
1617
use Elbformat\FieldHelperBundle\FieldHelper\NumberFieldHelper;
1718
use Elbformat\FieldHelperBundle\FieldHelper\RelationFieldHelper;
@@ -77,6 +78,11 @@ public function getImageFieldHelper(): ImageFieldHelper
7778
return $this->getFieldHelper(ImageFieldHelper::class);
7879
}
7980

81+
public function getMatrixFieldHelper(): MatrixFieldHelper
82+
{
83+
return $this->getFieldHelper(MatrixFieldHelper::class);
84+
}
85+
8086
public function getNetgenTagsFieldHelper(): NetgenTagsFieldHelper
8187
{
8288
return $this->getFieldHelper(NetgenTagsFieldHelper::class);

src/Registry/RegistryInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Elbformat\FieldHelperBundle\FieldHelper\FieldHelperInterface;
99
use Elbformat\FieldHelperBundle\FieldHelper\FileFieldHelper;
1010
use Elbformat\FieldHelperBundle\FieldHelper\ImageFieldHelper;
11+
use Elbformat\FieldHelperBundle\FieldHelper\MatrixFieldHelper;
1112
use Elbformat\FieldHelperBundle\FieldHelper\NetgenTagsFieldHelper;
1213
use Elbformat\FieldHelperBundle\FieldHelper\NumberFieldHelper;
1314
use Elbformat\FieldHelperBundle\FieldHelper\RelationFieldHelper;
@@ -30,6 +31,7 @@ public function getBoolFieldHelper(): BoolFieldHelper;
3031
public function getDateTimeFieldHelper(): DateTimeFieldHelper;
3132
public function getFileFieldHelper(): FileFieldHelper;
3233
public function getImageFieldHelper(): ImageFieldHelper;
34+
public function getMatrixFieldHelper(): MatrixFieldHelper;
3335
public function getNetgenTagsFieldHelper(): NetgenTagsFieldHelper;
3436
public function getNumberFieldHelper(): NumberFieldHelper;
3537
public function getRelationFieldHelper(): RelationFieldHelper;

tests/DependencyInjection/Compile/FieldHelperPassTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public function testProcess(): void
2525
if (BoolFieldHelper::class !== array_keys($arg)[0]) {
2626
throw new \Exception('Invalid helper name: '.array_keys($arg)[0]);
2727
}
28-
if (! array_values($arg)[0] instanceof Reference) {
28+
if (!array_values($arg)[0] instanceof Reference) {
2929
throw new \Exception('Helper not a reference');
3030
}
3131
return ('elbformat_field_helper.field_helper.test' === (string) array_values($arg)[0]);
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Elbformat\FieldHelperBundle\Tests\FieldHelper;
6+
7+
use Elbformat\FieldHelperBundle\FieldHelper\MatrixFieldHelper;
8+
use eZ\Publish\API\Repository\Values\Content\Field;
9+
use eZ\Publish\Core\Repository\Values\Content\Content;
10+
use EzSystems\EzPlatformMatrixFieldtype\FieldType\Value as MatrixValue;
11+
use PHPUnit\Framework\TestCase;
12+
13+
/**
14+
* @author Hannes Giesenow <hannes.giesenow@elbformat.de>
15+
*/
16+
class MatrixFieldHelperTest extends TestCase
17+
{
18+
/**
19+
* @dataProvider isEmptyProvider
20+
*/
21+
public function testIsEmpty(MatrixValue $value, bool $expected): void
22+
{
23+
$fh = new MatrixFieldHelper();
24+
$content = $this->createContentFromValue($value);
25+
$this->assertSame($expected, $fh->isEmpty($content, 'matrix_field'));
26+
}
27+
28+
29+
public function isEmptyProvider(): array
30+
{
31+
return [
32+
[new MatrixValue(), true],
33+
[new MatrixValue([]), true],
34+
[new MatrixValue([new MatrixValue\Row(['x'])]), false],
35+
];
36+
}
37+
38+
protected function createContentFromValue(MatrixValue $value): Content
39+
{
40+
$field = new Field(['value' => $value]);
41+
42+
$content = $this->createMock(Content::class);
43+
$content->method('getField')->with('matrix_field')->willReturn($field);
44+
45+
return $content;
46+
}
47+
}

0 commit comments

Comments
 (0)