Skip to content

Commit d36d545

Browse files
authored
Merge pull request #1 from root-aza/master
Added CallableTypeBuilder
2 parents 0542cc5 + 2300c3c commit d36d545

File tree

5 files changed

+258
-0
lines changed

5 files changed

+258
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ You can see some [examples here](/example):
3737
- [02.custom-type](/example/03.types/02.custom-type.php)
3838
- [03.custom-type-template-arguments](/example/03.types/03.custom-type-template-arguments.php)
3939
- [04.custom-platform](/example/03.types/04.custom-platform.php)
40+
- [05.custom-type-callable](/example/03.types/05.custom-type-callable.php)
41+
- [06.custom-type-psr-container](/example/03.types/06.custom-type-psr-container.php)
4042
- [04.mapping](/example/04.mapping)
4143
- [01.reflection-mapping](/example/04.mapping/01.reflection-mapping.php)
4244
- [02.attribute-mapping](/example/04.mapping/02.attribute-mapping.php)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Psr\Container\ContainerInterface;
6+
use TypeLang\Mapper\Exception\Mapping\InvalidValueException;
7+
use TypeLang\Mapper\Mapper;
8+
use TypeLang\Mapper\Platform\DelegatePlatform;
9+
use TypeLang\Mapper\Platform\StandardPlatform;
10+
use TypeLang\Mapper\Runtime\Context;
11+
use TypeLang\Mapper\Type\Builder\CallableTypeBuilder;
12+
use TypeLang\Mapper\Type\TypeInterface;
13+
14+
require __DIR__ . '/../../vendor/autoload.php';
15+
16+
17+
class Container implements ContainerInterface
18+
{
19+
/**
20+
* @var array<non-empty-string, object>
21+
*/
22+
private array $services;
23+
24+
public function __construct()
25+
{
26+
$this->services = [
27+
'my-non-empty-type' => new MyNonEmptyStringType()
28+
];
29+
}
30+
31+
public function get(string $id)
32+
{
33+
return $this->services[$id] ?? throw new \RuntimeException("Service $id not found");
34+
}
35+
36+
public function has(string $id): bool
37+
{
38+
return array_key_exists($id, $this->services);
39+
}
40+
}
41+
42+
43+
44+
45+
// Add new type (must implement TypeInterface)
46+
class MyNonEmptyStringType implements TypeInterface
47+
{
48+
public function match(mixed $value, Context $context): bool
49+
{
50+
return \is_string($value) && $value !== '';
51+
}
52+
53+
public function cast(mixed $value, Context $context): string
54+
{
55+
if (\is_string($value) && $value !== '') {
56+
return $value;
57+
}
58+
59+
60+
throw new InvalidValueException(
61+
value: $value,
62+
path: $context->getPath(),
63+
template: 'Passed value cannot be empty, but {{value}} given',
64+
);
65+
}
66+
}
67+
68+
$container = new Container();
69+
70+
$mapper = new Mapper(new DelegatePlatform(
71+
// Extend existing platform (StandardPlatform)
72+
delegate: new StandardPlatform(),
73+
types: [
74+
// Additional type
75+
new CallableTypeBuilder('custom-string', static fn (): TypeInterface => $container->get('my-non-empty-type')),
76+
],
77+
));
78+
79+
$result = $mapper->normalize(['example', ''], 'list<custom-string>');
80+
81+
var_dump($result);
82+
//
83+
// expected exception:
84+
// TypeLang\Mapper\Exception\Mapping\InvalidIterableValueException:
85+
// Passed value "" on index 1 in ["example", ""] is invalid at $[1]
86+
//
87+
// previous exception:
88+
// TypeLang\Mapper\Exception\Mapping\InvalidValueException:
89+
// Passed value cannot be empty, but "" given at $[1]
90+
//
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Psr\Container\ContainerInterface;
6+
use TypeLang\Mapper\Exception\Mapping\InvalidValueException;
7+
use TypeLang\Mapper\Mapper;
8+
use TypeLang\Mapper\Platform\DelegatePlatform;
9+
use TypeLang\Mapper\Platform\StandardPlatform;
10+
use TypeLang\Mapper\Runtime\Context;
11+
use TypeLang\Mapper\Type\Builder\CallableTypeBuilder;
12+
use TypeLang\Mapper\Type\Builder\PsrContainerTypeBuilder;
13+
use TypeLang\Mapper\Type\TypeInterface;
14+
15+
require __DIR__ . '/../../vendor/autoload.php';
16+
17+
18+
class Container implements ContainerInterface
19+
{
20+
/**
21+
* @var array<non-empty-string, object>
22+
*/
23+
private array $services;
24+
25+
public function __construct()
26+
{
27+
$this->services = [
28+
MyNonEmptyStringType::class => new MyNonEmptyStringType()
29+
];
30+
}
31+
32+
public function get(string $id)
33+
{
34+
return $this->services[$id] ?? throw new \RuntimeException("Service $id not found");
35+
}
36+
37+
public function has(string $id): bool
38+
{
39+
return array_key_exists($id, $this->services);
40+
}
41+
}
42+
43+
44+
// Add new type (must implement TypeInterface)
45+
class MyNonEmptyStringType implements TypeInterface
46+
{
47+
public function match(mixed $value, Context $context): bool
48+
{
49+
return \is_string($value) && $value !== '';
50+
}
51+
52+
public function cast(mixed $value, Context $context): string
53+
{
54+
if (\is_string($value) && $value !== '') {
55+
return $value;
56+
}
57+
58+
throw new InvalidValueException(
59+
value: $value,
60+
path: $context->getPath(),
61+
template: 'Passed value cannot be empty, but {{value}} given',
62+
);
63+
}
64+
}
65+
66+
$container = new Container();
67+
68+
$mapper = new Mapper(new DelegatePlatform(
69+
// Extend existing platform (StandardPlatform)
70+
delegate: new StandardPlatform(),
71+
types: [
72+
// Additional type
73+
new PsrContainerTypeBuilder('custom-string', MyNonEmptyStringType::class, $container),
74+
],
75+
));
76+
77+
$result = $mapper->normalize(['example', ''], 'list<custom-string>');
78+
79+
var_dump($result);
80+
//
81+
// expected exception:
82+
// TypeLang\Mapper\Exception\Mapping\InvalidIterableValueException:
83+
// Passed value "" on index 1 in ["example", ""] is invalid at $[1]
84+
//
85+
// previous exception:
86+
// TypeLang\Mapper\Exception\Mapping\InvalidValueException:
87+
// Passed value cannot be empty, but "" given at $[1]
88+
//
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\Mapper\Type\Builder;
6+
7+
use TypeLang\Mapper\Runtime\Parser\TypeParserInterface;
8+
use TypeLang\Mapper\Runtime\Repository\TypeRepositoryInterface;
9+
use TypeLang\Mapper\Type\TypeInterface;
10+
use TypeLang\Parser\Node\Stmt\TypeStatement;
11+
12+
/**
13+
* @template-extends NamedTypeBuilder<TypeInterface>
14+
*/
15+
class CallableTypeBuilder extends NamedTypeBuilder
16+
{
17+
/**
18+
* @param non-empty-array<non-empty-string>|non-empty-string $names
19+
* @param \Closure(): TypeInterface $factory
20+
*/
21+
public function __construct(
22+
array|string $names,
23+
protected readonly \Closure $factory,
24+
) {
25+
parent::__construct($names);
26+
}
27+
28+
public function build(TypeStatement $statement, TypeRepositoryInterface $types, TypeParserInterface $parser): TypeInterface
29+
{
30+
$this->expectNoShapeFields($statement);
31+
$this->expectNoTemplateArguments($statement);
32+
33+
return ($this->factory)();
34+
}
35+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\Mapper\Type\Builder;
6+
7+
use Psr\Container\ContainerInterface;
8+
use TypeLang\Mapper\Runtime\Parser\TypeParserInterface;
9+
use TypeLang\Mapper\Runtime\Repository\TypeRepositoryInterface;
10+
use TypeLang\Mapper\Type\TypeInterface;
11+
use TypeLang\Parser\Node\Stmt\TypeStatement;
12+
13+
/**
14+
* @template-extends NamedTypeBuilder<TypeInterface>
15+
*/
16+
class PsrContainerTypeBuilder extends NamedTypeBuilder
17+
{
18+
/**
19+
* @param non-empty-array<non-empty-string>|non-empty-string $names
20+
* @param class-string<TypeInterface> $serviceId
21+
*/
22+
public function __construct(
23+
array|string $names,
24+
protected readonly string $serviceId,
25+
protected readonly ContainerInterface $container,
26+
) {
27+
parent::__construct($names);
28+
}
29+
30+
public function build(TypeStatement $statement, TypeRepositoryInterface $types, TypeParserInterface $parser): TypeInterface
31+
{
32+
$this->expectNoShapeFields($statement);
33+
$this->expectNoTemplateArguments($statement);
34+
35+
$service = $this->container->get($this->serviceId);
36+
37+
if (!$service instanceof TypeInterface) {
38+
throw new \RuntimeException("Type service '{$this->serviceId}' does not implement TypeLang\Mapper\Type\TypeInterface");
39+
}
40+
41+
return $service;
42+
}
43+
}

0 commit comments

Comments
 (0)