Skip to content

Commit 433e7e2

Browse files
authored
Merge pull request sitegeist#121 from sitegeist/feature/conversionInterface
Respect class inheritance during component argument conversion
2 parents 87a587d + a727a4b commit 433e7e2

File tree

7 files changed

+131
-54
lines changed

7 files changed

+131
-54
lines changed

Classes/Utility/ComponentArgumentConverter.php

+33-35
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111
use SMS\FluidComponents\Interfaces\ConstructibleFromInteger;
1212
use SMS\FluidComponents\Interfaces\ConstructibleFromNull;
1313
use SMS\FluidComponents\Interfaces\ConstructibleFromString;
14-
use TYPO3\CMS\Core\Resource\File;
15-
use TYPO3\CMS\Core\Resource\FileReference;
16-
use TYPO3\CMS\Core\Resource\ProcessedFile;
14+
use TYPO3\CMS\Core\Resource\FileInterface;
15+
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
1716

1817
class ComponentArgumentConverter implements \TYPO3\CMS\Core\SingletonInterface
1918
{
@@ -53,26 +52,14 @@ class ComponentArgumentConverter implements \TYPO3\CMS\Core\SingletonInterface
5352
ConstructibleFromDateTimeImmutable::class,
5453
'fromDateTimeImmutable'
5554
],
56-
FileReference::class => [
55+
FileInterface::class => [
5756
ConstructibleFromFileInterface::class,
5857
'fromFileInterface'
5958
],
60-
File::class => [
61-
ConstructibleFromFileInterface::class,
62-
'fromFileInterface'
63-
],
64-
ProcessedFile::class => [
65-
ConstructibleFromFileInterface::class,
66-
'fromFileInterface'
67-
],
68-
\TYPO3\CMS\Extbase\Domain\Model\FileReference::class => [
69-
ConstructibleFromExtbaseFile::class,
70-
'fromExtbaseFile'
71-
],
72-
\GeorgRinger\News\Domain\Model\FileReference::class => [
59+
FileReference::class => [
7360
ConstructibleFromExtbaseFile::class,
7461
'fromExtbaseFile'
75-
],
62+
]
7663
];
7764

7865
/**
@@ -174,13 +161,13 @@ public function resolveTypeAlias(string $type): string
174161
*
175162
* @param string $givenType
176163
* @param string $toType
177-
* @return boolean
164+
* @return array information about conversion or empty array
178165
*/
179-
public function canTypeBeConvertedToType(string $givenType, string $toType): bool
166+
public function canTypeBeConvertedToType(string $givenType, string $toType): array
180167
{
181168
// No need to convert equal types
182169
if ($givenType === $toType) {
183-
return false;
170+
return [];
184171
}
185172

186173
// Has this check already been computed?
@@ -189,23 +176,33 @@ public function canTypeBeConvertedToType(string $givenType, string $toType): boo
189176
}
190177

191178
// Check if a constructor interface exists for the given type
192-
// Check if the target type is a PHP class
193-
$canBeConverted = false;
194-
if (isset($this->conversionInterfaces[$givenType]) && class_exists($toType)) {
195-
// Check if the target type implements the constructor interface
196-
// required for conversion
197-
$canBeConverted = is_subclass_of(
198-
$toType,
199-
$this->conversionInterfaces[$givenType][0]
200-
);
179+
// Check if the target type implements the constructor interface
180+
// required for conversion
181+
$conversionInfo = [];
182+
if (isset($this->conversionInterfaces[$givenType][0]) &&
183+
is_subclass_of($toType, $this->conversionInterfaces[$givenType][0])
184+
) {
185+
$conversionInfo = $this->conversionInterfaces[$givenType];
201186
} elseif ($this->isCollectionType($toType) && $this->isAccessibleArray($givenType)) {
202-
$canBeConverted = true;
187+
$conversionInfo = $this->conversionInterfaces['array'] ?? [];
188+
}
189+
190+
if (!$conversionInfo && class_exists($givenType)) {
191+
$parentClasses = array_merge(class_parents($givenType), class_implements($givenType));
192+
if (is_array($parentClasses)) {
193+
foreach ($parentClasses as $className) {
194+
if ($this->canTypeBeConvertedToType($className, $toType)) {
195+
$conversionInfo = $this->conversionInterfaces[$className];
196+
break;
197+
}
198+
}
199+
}
203200
}
204201

205202
// Add to runtime cache
206-
$this->conversionCache[$givenType . '|' . $toType] = $canBeConverted;
203+
$this->conversionCache[$givenType . '|' . $toType] = $conversionInfo;
207204

208-
return $canBeConverted;
205+
return $conversionInfo;
209206
}
210207

211208
/**
@@ -221,7 +218,8 @@ public function convertValueToType($value, string $toType)
221218
$givenType = is_object($value) ? get_class($value) : gettype($value);
222219

223220
// Skip if the type can't be converted
224-
if (!$this->canTypeBeConvertedToType($givenType, $toType)) {
221+
$conversionInfo = $this->canTypeBeConvertedToType($givenType, $toType);
222+
if (!$conversionInfo) {
225223
return $value;
226224
}
227225

@@ -235,7 +233,7 @@ public function convertValueToType($value, string $toType)
235233
}
236234

237235
// Call alternative constructor provided by interface
238-
$constructor = $this->conversionInterfaces[$givenType][1];
236+
$constructor = $conversionInfo[1];
239237
return $toType::$constructor($value);
240238
}
241239

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
namespace SMS\FluidComponents\Tests\Helpers\ComponentArgumentConverter;
3+
4+
class BaseObject
5+
{
6+
public $value;
7+
8+
public function __construct(string $value)
9+
{
10+
$this->value = $value;
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
namespace SMS\FluidComponents\Tests\Helpers\ComponentArgumentConverter;
3+
4+
interface BaseObjectConversionInterface
5+
{
6+
public static function fromBaseObject(BaseObject $value);
7+
}

Tests/Helpers/ComponentArgumentConverter/DummyValue.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
namespace SMS\FluidComponents\Tests\Helpers\ComponentArgumentConverter;
33

4-
class DummyValue implements DummyConversionInterface
4+
class DummyValue implements DummyConversionInterface, BaseObjectConversionInterface
55
{
66
public $value;
77

@@ -14,4 +14,9 @@ public static function fromString(string $value)
1414
{
1515
return new static($value);
1616
}
17+
18+
public static function fromBaseObject(BaseObject $object)
19+
{
20+
return new static($object->value);
21+
}
1722
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
namespace SMS\FluidComponents\Tests\Helpers\ComponentArgumentConverter;
3+
4+
class SpecificObject extends BaseObject
5+
{
6+
}

Tests/Unit/Utility/ComponentArgumentConverterTest.php

+62-17
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@
22

33
namespace SMS\FluidComponents\Tests\Unit\Utility;
44

5+
use SMS\FluidComponents\Interfaces\ConstructibleFromArray;
56
use SMS\FluidComponents\Utility\ComponentArgumentConverter;
6-
use SMS\FluidComponents\Tests\Helpers\ComponentArgumentConverter\DummyConversionInterface;
7+
use SMS\FluidComponents\Tests\Helpers\ComponentArgumentConverter\BaseObject;
78
use SMS\FluidComponents\Tests\Helpers\ComponentArgumentConverter\DummyValue;
9+
use SMS\FluidComponents\Tests\Helpers\ComponentArgumentConverter\SpecificObject;
10+
use SMS\FluidComponents\Tests\Helpers\ComponentArgumentConverter\DummyConversionInterface;
11+
use SMS\FluidComponents\Tests\Helpers\ComponentArgumentConverter\BaseObjectConversionInterface;
812

913
class ComponentArgumentConverterTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
1014
{
15+
protected ComponentArgumentConverter $converter;
16+
1117
protected function setUp(): void
1218
{
1319
parent::setUp();
@@ -61,8 +67,9 @@ public function resolveTypeAliasCollection()
6167
public function addRemoveConversionInterface()
6268
{
6369
$this->assertEquals(
64-
false,
65-
$this->converter->canTypeBeConvertedToType('string', DummyValue::class)
70+
[],
71+
$this->converter->canTypeBeConvertedToType('string', DummyValue::class),
72+
'before conversion interface registration'
6673
);
6774

6875
$this->converter->addConversionInterface(
@@ -72,15 +79,17 @@ public function addRemoveConversionInterface()
7279
);
7380

7481
$this->assertEquals(
75-
true,
76-
$this->converter->canTypeBeConvertedToType('string', DummyValue::class)
82+
[DummyConversionInterface::class, 'fromString'],
83+
$this->converter->canTypeBeConvertedToType('string', DummyValue::class),
84+
'after conversion interface registration'
7785
);
7886

7987
$this->converter->removeConversionInterface('string');
8088

8189
$this->assertEquals(
82-
false,
83-
$this->converter->canTypeBeConvertedToType('string', DummyValue::class)
90+
[],
91+
$this->converter->canTypeBeConvertedToType('string', DummyValue::class),
92+
'after conversion interface removal'
8493
);
8594
}
8695

@@ -94,43 +103,58 @@ public function canTypeBeConvertedToType()
94103
DummyConversionInterface::class,
95104
'fromString'
96105
);
106+
$this->converter->addConversionInterface(
107+
BaseObject::class,
108+
BaseObjectConversionInterface::class,
109+
'fromBaseObject'
110+
);
97111

98112
$this->assertEquals(
99-
true,
113+
[DummyConversionInterface::class, 'fromString'],
100114
$this->converter->canTypeBeConvertedToType('string', DummyValue::class)
101115
);
102116
$this->assertEquals(
103-
false,
117+
[],
104118
$this->converter->canTypeBeConvertedToType(DummyValue::class, 'string')
105119
);
106120
$this->assertEquals(
107-
false,
121+
[],
108122
$this->converter->canTypeBeConvertedToType('array', DummyValue::class)
109123
);
110124
$this->assertEquals(
111-
false,
125+
[],
112126
$this->converter->canTypeBeConvertedToType(DummyValue::class, 'array')
113127
);
114128

115129
// No conversion necessary
116130
$this->assertEquals(
117-
false,
131+
[],
118132
$this->converter->canTypeBeConvertedToType('string', 'string')
119133
);
120134

121135
// Collections
122136
$this->assertEquals(
123-
true,
137+
[ConstructibleFromArray::class, 'fromArray'],
124138
$this->converter->canTypeBeConvertedToType('array', DummyValue::class . '[]')
125139
);
126140
$this->assertEquals(
127-
true,
141+
[ConstructibleFromArray::class, 'fromArray'],
128142
$this->converter->canTypeBeConvertedToType(\ArrayIterator::class, DummyValue::class . '[]')
129143
);
130144
$this->assertEquals(
131-
false,
145+
[],
132146
$this->converter->canTypeBeConvertedToType('string', DummyValue::class . '[]')
133147
);
148+
149+
// Class inheritance
150+
$this->assertEquals(
151+
[BaseObjectConversionInterface::class, 'fromBaseObject'],
152+
$this->converter->canTypeBeConvertedToType(BaseObject::class, DummyValue::class)
153+
);
154+
$this->assertEquals(
155+
[BaseObjectConversionInterface::class, 'fromBaseObject'],
156+
$this->converter->canTypeBeConvertedToType(SpecificObject::class, DummyValue::class)
157+
);
134158
}
135159

136160
/**
@@ -146,13 +170,13 @@ public function canTypeBeConvertedToTypeCached()
146170

147171
// Uncached result
148172
$this->assertEquals(
149-
true,
173+
[DummyConversionInterface::class, 'fromString'],
150174
$this->converter->canTypeBeConvertedToType('string', DummyValue::class)
151175
);
152176

153177
// Cached result
154178
$this->assertEquals(
155-
true,
179+
[DummyConversionInterface::class, 'fromString'],
156180
$this->converter->canTypeBeConvertedToType('string', DummyValue::class)
157181
);
158182
}
@@ -196,6 +220,27 @@ public function convertValueToType()
196220
);
197221
}
198222

223+
/**
224+
* @test
225+
*/
226+
public function convertChildClassToType()
227+
{
228+
$this->converter->addConversionInterface(
229+
BaseObject::class,
230+
BaseObjectConversionInterface::class,
231+
'fromBaseObject'
232+
);
233+
234+
$this->assertEquals(
235+
'My value',
236+
$this->converter->convertValueToType(new BaseObject('My value'), DummyValue::class)->value
237+
);
238+
$this->assertEquals(
239+
'My value',
240+
$this->converter->convertValueToType(new SpecificObject('My value'), DummyValue::class)->value
241+
);
242+
}
243+
199244
/**
200245
* @test
201246
*/

composer.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@
3636
},
3737
"config": {
3838
"vendor-dir": ".Build/vendor",
39-
"bin-dir": ".Build/bin"
39+
"bin-dir": ".Build/bin",
40+
"allow-plugins": {
41+
"typo3/class-alias-loader": true,
42+
"typo3/cms-composer-installers": true
43+
}
4044
},
4145
"extra": {
4246
"typo3/cms": {

0 commit comments

Comments
 (0)