Skip to content

Commit 44c5f13

Browse files
committed
feat: improve cache warmup
The Warmup will now recursively handle interface and their class implementations. It is also done in a more clever way: instead of warming up all properties and constructors, it takes only what is needed.
1 parent 90dc586 commit 44c5f13

5 files changed

Lines changed: 133 additions & 26 deletions

File tree

src/Cache/Warmup/RecursiveCacheWarmupService.php

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66

77
use CuyZ\Valinor\Cache\Exception\InvalidSignatureToWarmup;
88
use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
9+
use CuyZ\Valinor\Mapper\Object\Factory\ObjectBuilderFactory;
10+
use CuyZ\Valinor\Mapper\Tree\Builder\ObjectImplementations;
911
use CuyZ\Valinor\Type\CompositeType;
1012
use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
1113
use CuyZ\Valinor\Type\Parser\TypeParser;
1214
use CuyZ\Valinor\Type\Type;
1315
use CuyZ\Valinor\Type\Types\ClassType;
16+
use CuyZ\Valinor\Type\Types\InterfaceType;
1417

1518
use function in_array;
1619

@@ -19,15 +22,25 @@ final class RecursiveCacheWarmupService
1922
{
2023
private TypeParser $parser;
2124

25+
private ObjectImplementations $implementations;
26+
2227
private ClassDefinitionRepository $classDefinitionRepository;
2328

29+
private ObjectBuilderFactory $objectBuilderFactory;
30+
2431
/** @var list<class-string> */
2532
private array $classesWarmedUp = [];
2633

27-
public function __construct(TypeParser $parser, ClassDefinitionRepository $classDefinitionRepository)
28-
{
34+
public function __construct(
35+
TypeParser $parser,
36+
ObjectImplementations $implementations,
37+
ClassDefinitionRepository $classDefinitionRepository,
38+
ObjectBuilderFactory $objectBuilderFactory
39+
) {
2940
$this->parser = $parser;
41+
$this->implementations = $implementations;
3042
$this->classDefinitionRepository = $classDefinitionRepository;
43+
$this->objectBuilderFactory = $objectBuilderFactory;
3144
}
3245

3346
public function warmup(string ...$signatures): void
@@ -43,6 +56,10 @@ public function warmup(string ...$signatures): void
4356

4457
private function warmupType(Type $type): void
4558
{
59+
if ($type instanceof InterfaceType) {
60+
$this->warmupInterfaceType($type);
61+
}
62+
4663
if ($type instanceof ClassType) {
4764
$this->warmupClassType($type);
4865
}
@@ -54,6 +71,17 @@ private function warmupType(Type $type): void
5471
}
5572
}
5673

74+
private function warmupInterfaceType(InterfaceType $type): void
75+
{
76+
$function = $this->implementations->function($type->className());
77+
78+
$this->warmupType($function->returnType());
79+
80+
foreach ($function->parameters() as $parameter) {
81+
$this->warmupType($parameter->type());
82+
}
83+
}
84+
5785
private function warmupClassType(ClassType $type): void
5886
{
5987
if (in_array($type->className(), $this->classesWarmedUp, true)) {
@@ -63,16 +91,11 @@ private function warmupClassType(ClassType $type): void
6391
$this->classesWarmedUp[] = $type->className();
6492

6593
$classDefinition = $this->classDefinitionRepository->for($type);
94+
$objectBuilders = $this->objectBuilderFactory->for($classDefinition);
6695

67-
foreach ($classDefinition->properties() as $property) {
68-
$this->warmupType($property->type());
69-
}
70-
71-
foreach ($classDefinition->methods() as $method) {
72-
$this->warmupType($method->returnType());
73-
74-
foreach ($method->parameters() as $parameter) {
75-
$this->warmupType($parameter->type());
96+
foreach ($objectBuilders as $builder) {
97+
foreach ($builder->describeArguments() as $argument) {
98+
$this->warmupType($argument->type());
7699
}
77100
}
78101
}

src/Library/Container.php

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,7 @@ public function __construct(Settings $settings)
117117

118118
$builder = new InterfaceNodeBuilder(
119119
$builder,
120-
new ObjectImplementations(
121-
new FunctionsContainer(
122-
$this->get(FunctionDefinitionRepository::class),
123-
$settings->interfaceMapping
124-
),
125-
$this->get(TypeParser::class),
126-
),
120+
$this->get(ObjectImplementations::class),
127121
$this->get(ClassDefinitionRepository::class),
128122
$this->get(ObjectBuilderFactory::class),
129123
$settings->flexible
@@ -145,6 +139,14 @@ public function __construct(Settings $settings)
145139
return new ErrorCatcherNodeBuilder($builder);
146140
},
147141

142+
ObjectImplementations::class => fn () => new ObjectImplementations(
143+
new FunctionsContainer(
144+
$this->get(FunctionDefinitionRepository::class),
145+
$settings->interfaceMapping
146+
),
147+
$this->get(TypeParser::class),
148+
),
149+
148150
ObjectBuilderFactory::class => function () use ($settings) {
149151
$constructors = new FunctionsContainer(
150152
$this->get(FunctionDefinitionRepository::class),
@@ -213,7 +215,9 @@ public function __construct(Settings $settings)
213215

214216
RecursiveCacheWarmupService::class => fn () => new RecursiveCacheWarmupService(
215217
$this->get(TypeParser::class),
218+
$this->get(ObjectImplementations::class),
216219
$this->get(ClassDefinitionRepository::class),
220+
$this->get(ObjectBuilderFactory::class)
217221
),
218222

219223
CacheInterface::class => function () use ($settings) {

src/Type/Types/ClassStringType.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace CuyZ\Valinor\Type\Types;
66

7+
use CuyZ\Valinor\Type\CompositeType;
78
use CuyZ\Valinor\Type\ObjectType;
89
use CuyZ\Valinor\Type\StringType;
910
use CuyZ\Valinor\Type\Type;
@@ -18,7 +19,7 @@
1819
use function method_exists;
1920

2021
/** @api */
21-
final class ClassStringType implements StringType
22+
final class ClassStringType implements StringType, CompositeType
2223
{
2324
/** @var ObjectType|UnionType|null */
2425
private ?Type $subType;
@@ -130,6 +131,19 @@ public function subType(): ?Type
130131
return $this->subType;
131132
}
132133

134+
public function traverse(): iterable
135+
{
136+
if (! $this->subType) {
137+
return [];
138+
}
139+
140+
yield $this->subType;
141+
142+
if ($this->subType instanceof CompositeType) {
143+
yield from $this->subType->traverse();
144+
}
145+
}
146+
133147
public function __toString(): string
134148
{
135149
return $this->signature;

tests/Integration/Cache/CacheWarmupTest.php

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,44 @@ protected function setUp(): void
2424
$this->mapper = (new MapperBuilder())->withCache($this->cache);
2525
}
2626

27-
public function test_will_warmup_type_parser_cache(): void
27+
public function test_will_warmup_type_parser_cache_for_object_with_properties(): void
2828
{
29-
$this->mapper->warmup(ObjectToWarmup::class);
30-
$this->mapper->warmup(ObjectToWarmup::class, SomeObjectJ::class);
29+
$this->mapper->warmup(ObjectToWarmupWithProperties::class);
30+
$this->mapper->warmup(ObjectToWarmupWithProperties::class, SomeObjectJ::class);
3131

32-
self::assertSame(11, $this->cache->countEntries());
33-
self::assertSame(11, $this->cache->timeSetWasCalled());
32+
self::assertSame(10, $this->cache->countEntries());
33+
self::assertSame(10, $this->cache->timeSetWasCalled());
34+
}
35+
36+
public function test_will_warmup_type_parser_cache_for_object_with_constructor(): void
37+
{
38+
// @PHP8.1 first-class callable syntax
39+
$mapper = $this->mapper->registerConstructor(
40+
[ObjectToWarmupWithConstructors::class, 'constructorA'],
41+
[ObjectToWarmupWithConstructors::class, 'constructorB'],
42+
);
43+
44+
$mapper->warmup(ObjectToWarmupWithConstructors::class);
45+
$mapper->warmup(ObjectToWarmupWithConstructors::class, SomeObjectC::class);
46+
47+
self::assertSame(7, $this->cache->countEntries());
48+
self::assertSame(7, $this->cache->timeSetWasCalled());
49+
}
50+
51+
public function test_will_warmup_type_parser_cache_for_interface(): void
52+
{
53+
$mapper = $this->mapper
54+
->infer(
55+
SomeInterface::class,
56+
/** @return class-string<ObjectToWarmupWithProperties|ObjectToWarmupWithConstructors> */
57+
fn (string $foo, SomeObjectI $objectI) => $foo === 'foo' ? ObjectToWarmupWithProperties::class : ObjectToWarmupWithConstructors::class
58+
);
59+
60+
$mapper->warmup(SomeInterface::class);
61+
$mapper->warmup(SomeInterface::class, SomeObjectJ::class);
62+
63+
self::assertSame(13, $this->cache->countEntries());
64+
self::assertSame(13, $this->cache->timeSetWasCalled());
3465
}
3566

3667
public function test_warmup_invalid_signature_throws_exception(): void
@@ -43,7 +74,11 @@ public function test_warmup_invalid_signature_throws_exception(): void
4374
}
4475
}
4576

46-
final class ObjectToWarmup
77+
interface SomeInterface
78+
{
79+
}
80+
81+
final class ObjectToWarmupWithProperties implements SomeInterface
4782
{
4883
public string $string;
4984

@@ -70,12 +105,25 @@ final class ObjectToWarmup
70105
/** @var SomeObjectG&DateTimeInterface */
71106
public object $intersectionOfObjects;
72107

73-
public static function someMethod(string $string, SomeObjectH $object): SomeObjectI
108+
public static function someUnnecessaryMethod(string $string, SomeObjectH $object): SomeObjectI
74109
{
75110
return new SomeObjectI();
76111
}
77112
}
78113

114+
final class ObjectToWarmupWithConstructors implements SomeInterface
115+
{
116+
public static function constructorA(string $string, SomeObjectA $objectA): self
117+
{
118+
return new self();
119+
}
120+
121+
public static function constructorB(string $string, SomeObjectB $objectb): self
122+
{
123+
return new self();
124+
}
125+
}
126+
79127
class SomeObjectA
80128
{
81129
}

tests/Unit/Type/Types/ClassStringTypeTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace CuyZ\Valinor\Tests\Unit\Type\Types;
66

7+
use CuyZ\Valinor\Tests\Fake\Type\FakeObjectCompositeType;
78
use CuyZ\Valinor\Tests\Fake\Type\FakeObjectType;
89
use CuyZ\Valinor\Tests\Fake\Type\FakeType;
910
use CuyZ\Valinor\Tests\Fixture\Object\StringableObject;
@@ -259,4 +260,21 @@ public function test_does_not_match_union_containing_invalid_type(): void
259260

260261
self::assertFalse($classStringType->matches($unionType));
261262
}
263+
264+
public function test_traverse_type_yields_types_recursively(): void
265+
{
266+
$subTypeA = new FakeType();
267+
$subTypeB = new FakeType();
268+
$objectTypeA = new FakeObjectCompositeType(stdClass::class, ['Template' => $subTypeA]);
269+
$objectTypeB = new FakeObjectCompositeType(stdClass::class, ['Template' => $subTypeB]);
270+
$unionType = new UnionType($objectTypeA, $objectTypeB);
271+
272+
$type = new ClassStringType($unionType);
273+
274+
self::assertContains($unionType, $type->traverse());
275+
self::assertContains($subTypeA, $type->traverse());
276+
self::assertContains($subTypeB, $type->traverse());
277+
self::assertContains($objectTypeA, $type->traverse());
278+
self::assertContains($objectTypeB, $type->traverse());
279+
}
262280
}

0 commit comments

Comments
 (0)