Skip to content

Commit 7b540b1

Browse files
committed
docs(Typecast): add comment to the class; fix CS; update psalm baseline
1 parent 4d50868 commit 7b540b1

File tree

3 files changed

+103
-40
lines changed

3 files changed

+103
-40
lines changed

psalm-baseline.xml

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,12 @@
245245
<code><![CDATA[$this->waitScope]]></code>
246246
</InvalidPropertyAssignmentValue>
247247
</file>
248+
<file src="src/Config/RelationConfig.php">
249+
<TooManyArguments>
250+
<code><![CDATA[new Autowire($loader)]]></code>
251+
<code><![CDATA[new Autowire($relation)]]></code>
252+
</TooManyArguments>
253+
</file>
248254
<file src="src/EntityManager.php">
249255
<ClassMustBeFinal>
250256
<code><![CDATA[EntityManager]]></code>
@@ -372,6 +378,12 @@
372378
</ClassMustBeFinal>
373379
</file>
374380
<file src="src/Factory.php">
381+
<InvalidArgument>
382+
<code><![CDATA[$database]]></code>
383+
</InvalidArgument>
384+
<InvalidCast>
385+
<code><![CDATA[$database]]></code>
386+
</InvalidCast>
375387
<InvalidPropertyAssignmentValue>
376388
<code><![CDATA[[
377389
SchemaInterface::REPOSITORY => Repository::class,
@@ -460,6 +472,9 @@
460472
<code><![CDATA[$class]]></code>
461473
<code><![CDATA[$e->getCode()]]></code>
462474
</PossiblyInvalidArgument>
475+
<TooFewArguments>
476+
<code><![CDATA[new Typecast($database)]]></code>
477+
</TooFewArguments>
463478
</file>
464479
<file src="src/Heap/Heap.php">
465480
<InvalidNullableReturnType>
@@ -1165,40 +1180,59 @@
11651180
</file>
11661181
<file src="src/Parser/Typecast.php">
11671182
<DocblockTypeContradiction>
1168-
<code><![CDATA[$type === 'int']]></code>
1169-
<code><![CDATA[$type === 'int' && (\is_int($value) || \preg_match('/^\\d++$/', $value) === 1)
1170-
=> $rule::tryFrom((int) $value)]]></code>
1171-
<code><![CDATA[$type === 'string']]></code>
1172-
<code><![CDATA[null]]></code>
1183+
<code><![CDATA[$reflection->getParameters()[1]]]></code>
11731184
</DocblockTypeContradiction>
11741185
<MixedArgument>
11751186
<code><![CDATA[$key]]></code>
11761187
<code><![CDATA[$value]]></code>
11771188
<code><![CDATA[$value]]></code>
11781189
</MixedArgument>
11791190
<MixedAssignment>
1180-
<code><![CDATA[$data[$column]]]></code>
11811191
<code><![CDATA[$data[$key]]]></code>
11821192
<code><![CDATA[$data[$key]]]></code>
11831193
<code><![CDATA[$rule]]></code>
1184-
<code><![CDATA[$value]]></code>
11851194
</MixedAssignment>
1195+
<MixedPropertyTypeCoercion>
1196+
<code><![CDATA[$this->casters]]></code>
1197+
<code><![CDATA[$this->casters]]></code>
1198+
<code><![CDATA[$this->casters]]></code>
1199+
<code><![CDATA[$this->casters]]></code>
1200+
<code><![CDATA[$this->casters]]></code>
1201+
</MixedPropertyTypeCoercion>
1202+
<MixedReturnStatement>
1203+
<code><![CDATA[\is_int($value) || \is_string($value) && \preg_match('/^\\d++$/', $value) === 1
1204+
? $rule::tryFrom((int) $value)
1205+
: null]]></code>
1206+
<code><![CDATA[\is_int($value) || \is_string($value) && \preg_match('/^\\d++$/', $value) === 1
1207+
? $rule::tryFrom((int) $value)
1208+
: null]]></code>
1209+
<code><![CDATA[\is_string($value) || \is_numeric($value)
1210+
? $rule::tryFrom((string) $value)
1211+
: null]]></code>
1212+
<code><![CDATA[\is_string($value) || \is_numeric($value)
1213+
? $rule::tryFrom((string) $value)
1214+
: null]]></code>
1215+
<code><![CDATA[\json_decode(
1216+
$value,
1217+
true,
1218+
512,
1219+
\JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_SUBSTITUTE,
1220+
)]]></code>
1221+
</MixedReturnStatement>
11861222
<PossiblyInvalidArgument>
11871223
<code><![CDATA[$e->getCode()]]></code>
1188-
<code><![CDATA[$value]]></code>
1224+
<code><![CDATA[$e->getCode()]]></code>
11891225
</PossiblyInvalidArgument>
11901226
<PossiblyUndefinedVariable>
11911227
<code><![CDATA[$key]]></code>
1228+
<code><![CDATA[$key]]></code>
11921229
</PossiblyUndefinedVariable>
1193-
<PropertyTypeCoercion>
1194-
<code><![CDATA[$this->enumClasses]]></code>
1195-
</PropertyTypeCoercion>
1196-
<RedundantCondition>
1197-
<code><![CDATA[\is_array($rule)
1198-
&& \count($rule) >= 2
1199-
&& \is_string($rule[0])
1200-
&& \is_string($rule[1])]]></code>
1201-
</RedundantCondition>
1230+
<RedundantConditionGivenDocblockType>
1231+
<code><![CDATA[$reflection->getParameters()[1]?->getType()]]></code>
1232+
</RedundantConditionGivenDocblockType>
1233+
<UnnecessaryVarAnnotation>
1234+
<code><![CDATA[class-string<\BackedEnum>]]></code>
1235+
</UnnecessaryVarAnnotation>
12021236
</file>
12031237
<file src="src/Reference/Promise.php">
12041238
<MixedArgument>

src/Parser/Typecast.php

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,37 @@
88
use Cycle\ORM\Exception\TypecastException;
99
use Cycle\Database\DatabaseInterface;
1010

11+
/**
12+
* Default typecasting class for ORM entities.
13+
*
14+
* This class handles casting data from database format to PHP types and vice versa.
15+
*
16+
* It supports various rule types including:
17+
* - Built-in primitives: 'int', 'bool', 'float', 'datetime'.
18+
* - JSON encoding/decoding: 'json'.
19+
* - Backed enums: Any class implementing BackedEnum
20+
* - Callable functions:
21+
* - Single-argument static factory: [ClassName::class, 'simple'].
22+
* - Callable with database instance: [ClassName::class, 'withDatabase']
23+
* - Callable with additional arguments: [ClassName::class, 'customArgs', ['value1', 'value2']].
24+
*
25+
* ```
26+
* class ClassName {
27+
* public static function simple(mixed $value): mixed { ... }
28+
*
29+
* public static function withDatabase(
30+
* mixed $value,
31+
* DatabaseInterface $database, // Will be injected automatically
32+
* ): mixed { ... }
33+
*
34+
* public static function customArgs(
35+
* mixed $value,
36+
* string $arg1, // 'value1' will be passed
37+
* string $arg2, // 'value2' will be passed
38+
* ): mixed { ... }
39+
* }
40+
* ```
41+
*/
1142
final class Typecast implements CastableInterface, UncastableInterface
1243
{
1344
private const RULES = ['int', 'bool', 'float', 'datetime', 'json'];
@@ -29,19 +60,18 @@ public function __construct(
2960

3061
public function setRules(array $rules): array
3162
{
32-
$invoker = null;
3363
foreach ($rules as $key => $rule) {
3464
// Static rules
3565
if (\in_array($rule, self::RULES, true)) {
3666
$this->casters[$key] = match ($rule) {
37-
'int' => static fn (mixed $value): int => (int) $value,
38-
'bool' => static fn (mixed $value): bool => (bool) $value,
39-
'float' => static fn (mixed $value): float => (float) $value,
40-
'datetime' => fn (mixed $value): \DateTimeImmutable => new \DateTimeImmutable(
67+
'int' => static fn(mixed $value): int => (int) $value,
68+
'bool' => static fn(mixed $value): bool => (bool) $value,
69+
'float' => static fn(mixed $value): float => (float) $value,
70+
'datetime' => fn(mixed $value): \DateTimeImmutable => new \DateTimeImmutable(
4171
$value,
4272
$this->database->getDriver()->getTimezone(),
4373
),
44-
'json' => static fn (mixed $value): array => \json_decode(
74+
'json' => static fn(mixed $value): array => \json_decode(
4575
$value,
4676
true,
4777
512,
@@ -50,7 +80,7 @@ public function setRules(array $rules): array
5080
};
5181

5282
if ($rule === 'json') {
53-
$this->uncaters[$key] = static fn (mixed $value): string => \json_encode(
83+
$this->uncaters[$key] = static fn(mixed $value): string => \json_encode(
5484
$value,
5585
\JSON_UNESCAPED_UNICODE | \JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_SUBSTITUTE,
5686
);
@@ -67,11 +97,11 @@ public function setRules(array $rules): array
6797

6898
$this->casters[$key] = $type === 'string'
6999
// String backed enum
70-
? static fn (mixed $value): ?\BackedEnum => \is_string($value) || \is_numeric($value)
100+
? static fn(mixed $value): ?\BackedEnum => \is_string($value) || \is_numeric($value)
71101
? $rule::tryFrom((string) $value)
72102
: null
73103
// Int backed enum
74-
: static fn (mixed $value): ?\BackedEnum => \is_int($value) || \is_string($value) && \preg_match('/^\\d++$/', $value) === 1
104+
: static fn(mixed $value): ?\BackedEnum => \is_int($value) || \is_string($value) && \preg_match('/^\\d++$/', $value) === 1
75105
? $rule::tryFrom((int) $value)
76106
: null;
77107

@@ -84,7 +114,7 @@ public function setRules(array $rules): array
84114
$closure = \Closure::fromCallable($rule);
85115
$this->casters[$key] = (new \ReflectionFunction($closure))->getNumberOfParameters() === 1
86116
? $closure
87-
: fn (mixed $value): mixed => $closure($value, $this->database);
117+
: fn(mixed $value): mixed => $closure($value, $this->database);
88118
unset($rules[$key]);
89119
continue;
90120
}

tests/ORM/Unit/Parser/TypecastTest.php

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,23 @@
1414
use Cycle\ORM\Tests\Fixtures\Uuid;
1515
use Mockery as m;
1616
use PHPUnit\Framework\TestCase;
17-
use ReflectionFunction;
1817

1918
class TypecastTest extends TestCase
2019
{
2120
private Typecast $typecast;
2221
private m\LegacyMockInterface|m\MockInterface|DatabaseInterface $db;
2322

23+
public static function callablesWithArgs(): iterable
24+
{
25+
yield [[StaticCallableRule::class, 'invoke'], ['bar'], true];
26+
yield [[StaticCallableRule::class, 'invoke'], [['bar']], true];
27+
yield [[StaticCallableRule::class, 'invoke'], ['argument' => ['bar']], true];
28+
yield [[StaticCallableRule::class, 'invokeVariadic'], ['foo' => 'bar'], true];
29+
yield [[StaticCallableRule::class, 'invokeWithoutDatabaseVariadic'], ['foo' => 'bar'], false];
30+
yield [[StaticCallableRule::class, 'invokeWithoutDatabaseVariadic'], [1, 2, 42], false];
31+
yield [[StaticCallableRule::class, 'invokeWithoutDatabase'], [69], false];
32+
}
33+
2434
public function testSetRules(): void
2535
{
2636
$rules = [
@@ -166,25 +176,14 @@ public function testCastCallableWithArguments(array $callable, array $args, bool
166176
$this->assertSame('baz', $result['value'], 'Value should be "baz"');
167177
$hasDatabase and $this->assertInstanceOf(DatabaseInterface::class, $result['database'], 'Database passed');
168178

169-
$isVariadic = (new ReflectionFunction(\Closure::fromCallable($callable)))->isVariadic();
179+
$isVariadic = (new \ReflectionFunction(\Closure::fromCallable($callable)))->isVariadic();
170180
$this->assertSame(
171181
$isVariadic ? $args : \array_values($args),
172182
$result['arguments'],
173183
'Arguments must be the same as passed',
174184
);
175185
}
176186

177-
public static function callablesWithArgs(): iterable
178-
{
179-
yield [[StaticCallableRule::class, 'invoke'], ['bar'], true];
180-
yield [[StaticCallableRule::class, 'invoke'], [['bar']], true];
181-
yield [[StaticCallableRule::class, 'invoke'], ['argument' => ['bar']], true];
182-
yield [[StaticCallableRule::class, 'invokeVariadic'], ['foo' => 'bar'], true];
183-
yield [[StaticCallableRule::class, 'invokeWithoutDatabaseVariadic'], ['foo' => 'bar'], false];
184-
yield [[StaticCallableRule::class, 'invokeWithoutDatabaseVariadic'], [1, 2, 42], false];
185-
yield [[StaticCallableRule::class, 'invokeWithoutDatabase'], [69], false];
186-
}
187-
188187
public function testCastJsonValue(): void
189188
{
190189
$this->typecast->setRules(['foo' => 'json', 'baz' => 'json']);

0 commit comments

Comments
 (0)