Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
},
"require": {
"php": ">=8.5",
"php-di/php-di": "^7.1",
"psr/container": "^2.0",
"respect/config": "^3.0",
"respect/fluent": "^2.0",
"respect/parameter": "^3.0",
"respect/string-formatter": "^1.7",
"respect/stringifier": "^3.0",
"symfony/polyfill-intl-idn": "^1.33",
Expand Down
297 changes: 113 additions & 184 deletions composer.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ SPDX-FileContributor: Alexandre Gomes Gaigalas <alganet@gmail.com>

## Container configuration

The `ContainerRegistry::createContainer()` method returns a [PHP-DI](https://php-di.org/) container. The definitions array follows the [PHP-DI definitions format](https://php-di.org/doc/php-definitions.html).
The `ContainerRegistry::createContainer()` method returns a [Respect\Config](https://github.com/Respect/Config) container, which is [PSR-11](https://www.php-fig.org/psr/psr-11/) compatible. Definitions may be plain values, closures, or Respect\Config's `Autowire`, `Instantiator`, and `Ref` helpers.

If you prefer to use a different container, `ContainerRegistry::setContainer()` accepts any [PSR-11](https://www.php-fig.org/psr/psr-11/) compatible container:
If you prefer to use a different container, `ContainerRegistry::setContainer()` accepts any PSR-11 compatible container:

```php
use Respect\Validation\ContainerRegistry;
Expand Down
12 changes: 4 additions & 8 deletions docs/messages/placeholder-conversion.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,16 @@ You can add custom modifiers by providing a custom `PlaceholderFormatter` to the
`ContainerRegistry`:

```php
use DI\Container;
use Respect\Config\Container;
use Respect\StringFormatter\Modifier;
use Respect\StringFormatter\PlaceholderFormatter;
use Respect\Validation\ContainerRegistry;

use function DI\factory;

ContainerRegistry::setContainer(
ContainerRegistry::createContainer([
PlaceholderFormatter::class => factory(
fn(Container $container) => new PlaceholderFormatter(
[],
new MyCustomModifier($container->get(Modifier::class)),
),
PlaceholderFormatter::class => static fn(Container $container) => new PlaceholderFormatter(
[],
new MyCustomModifier($container->get(Modifier::class)),
),
])
);
Expand Down
2 changes: 1 addition & 1 deletion docs/migrating-from-v2-to-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ The `Factory` class has been replaced by a dependency injection container approa
+ ContainerRegistry::setContainer($container);
```

The `ContainerRegistry::createContainer()` returns a [PHP-DI](https://php-di.org/) container. You can also use any PSR-11 compatible container with `ContainerRegistry::setContainer()`.
The `ContainerRegistry::createContainer()` returns a [Respect\Config](https://github.com/Respect/Config) container, which is [PSR-11](https://www.php-fig.org/psr/psr-11/) compatible. You can also use any PSR-11 compatible container with `ContainerRegistry::setContainer()`.

### Custom validators

Expand Down
1 change: 1 addition & 0 deletions docs/validators/Attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>
# Attributes

- `Attributes()`
- `Attributes(Resolver $resolver)`

Validates the PHP attributes defined in the properties of the input.

Expand Down
1 change: 1 addition & 0 deletions docs/validators/Uuid.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ SPDX-FileContributor: steven.lewis <stevenlewis@gowebprint.com>

- `Uuid()`
- `Uuid(int $version)`
- `Uuid(int $version, UuidFactory $uuidFactory)`

Validates whether the input is a valid UUID. It also supports validation of
specific versions 1 to 8.
Expand Down
4 changes: 2 additions & 2 deletions src-dev/Commands/LintMixinCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int
config: $config,
scanner: $scanner,
methodBuilder: new MethodBuilder(
excludedTypePrefixes: ['Sokil', 'Egulias'],
excludedTypeNames: ['finfo'],
excludedTypePrefixes: ['Sokil', 'Egulias', 'Ramsey', 'libphonenumber'],
excludedTypeNames: ['Respect\\Parameter\\Resolver'],
),
interfaces: [
new InterfaceConfig(
Expand Down
72 changes: 72 additions & 0 deletions src/AutowiringLookup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

/*
* SPDX-License-Identifier: MIT
* SPDX-FileCopyrightText: (c) Respect Project Contributors
* SPDX-FileContributor: Alexandre Gomes Gaigalas <alganet@gmail.com>
*/

declare(strict_types=1);

namespace Respect\Validation;

use Respect\Fluent\Exceptions\CouldNotCreate;
use Respect\Fluent\Factories\NamespaceLookup;
use Respect\Fluent\FluentFactory;
use Respect\Fluent\FluentNode;
use Respect\Fluent\FluentResolver;
use Respect\Parameter\Resolver;
use Throwable;

use function sprintf;

final readonly class AutowiringLookup implements FluentFactory
{
public function __construct(
private NamespaceLookup $lookup,
private FluentResolver $resolver,
private Resolver $parameterResolver,
) {
}

public function withNamespace(string $namespace): static
{
return clone ($this, ['lookup' => $this->lookup->withNamespace($namespace)]);
}

/** @param array<int|string, mixed> $arguments */
public function create(string $name, array $arguments = []): object
{
$spec = $this->resolver->resolve(new FluentNode($name, $arguments));

$instance = $this->instantiate($spec->name, $spec->arguments);

$wrapper = $spec->wrapper;
while ($wrapper !== null) {
$instance = $this->instantiate($wrapper->name, [...$wrapper->arguments, $instance]);
$wrapper = $wrapper->wrapper;
}

return $instance;
}

/** @param array<int|string, mixed> $arguments */
private function instantiate(string $name, array $arguments): object
{
$reflection = $this->lookup->resolve($name);

$constructor = $reflection->getConstructor();
try {
if ($constructor === null) {
return $reflection->newInstanceArgs($arguments);
}

return $reflection->newInstanceArgs($this->parameterResolver->resolve($constructor, $arguments));
} catch (Throwable $exception) {
throw new CouldNotCreate(
sprintf('Could not instantiate "%s": %s', $name, $exception->getMessage()),
previous: $exception,
);
}
}
}
86 changes: 48 additions & 38 deletions src/ContainerRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@

namespace Respect\Validation;

use DI\Container;
use libphonenumber\PhoneNumberUtil;
use Psr\Container\ContainerInterface;
use Respect\Fluent\Factories\ComposingLookup;
use Ramsey\Uuid\UuidFactory;
use Respect\Config\Autowire;
use Respect\Config\Container;
use Respect\Config\Instantiator;
use Respect\Fluent\Factories\NamespaceLookup;
use Respect\Fluent\Resolvers\ComposableMap;
use Respect\Fluent\Resolvers\Ucfirst;
use Respect\Parameter\ContainerResolver;
use Respect\Parameter\Resolver;
use Respect\StringFormatter\BypassTranslator;
use Respect\StringFormatter\Modifier;
use Respect\StringFormatter\Modifiers\FormatterModifier;
Expand Down Expand Up @@ -45,12 +49,12 @@
use Respect\Validation\Message\Renderer;
use Respect\Validation\Message\TemplateRegistry;
use Respect\Validation\Mixins\PrefixConstants;
use Sokil\IsoCodes\Database\Countries;
use Sokil\IsoCodes\Database\Currencies;
use Sokil\IsoCodes\Database\Languages;
use Sokil\IsoCodes\Database\Subdivisions;
use Symfony\Contracts\Translation\TranslatorInterface;

use function DI\autowire;
use function DI\create;
use function DI\factory;

final class ContainerRegistry
{
private static ContainerInterface|null $container = null;
Expand All @@ -59,50 +63,56 @@ final class ContainerRegistry
public static function createContainer(array $definitions = []): Container
{
return new Container($definitions + [
PhoneNumberUtil::class => factory(static fn() => PhoneNumberUtil::getInstance()),
TemplateRegistry::class => create(TemplateRegistry::class),
TemplateResolver::class => autowire(TemplateResolver::class),
TranslatorInterface::class => autowire(BypassTranslator::class),
Renderer::class => autowire(InterpolationRenderer::class),
ResultFilter::class => create(OnlyFailedChildrenResultFilter::class),
'respect.validation.formatter.message' => autowire(FirstResultStringFormatter::class),
'respect.validation.formatter.full_message' => autowire(NestedListStringFormatter::class),
'respect.validation.formatter.messages' => autowire(NestedArrayFormatter::class),
Countries::class => new Instantiator(Countries::class),
Currencies::class => new Instantiator(Currencies::class),
Languages::class => new Instantiator(Languages::class),
Subdivisions::class => new Instantiator(Subdivisions::class),
UuidFactory::class => new Instantiator(UuidFactory::class),
PhoneNumberUtil::class => static fn() => PhoneNumberUtil::getInstance(),
TemplateRegistry::class => new Instantiator(TemplateRegistry::class),
TemplateResolver::class => new Autowire(TemplateResolver::class),
TranslatorInterface::class => new Autowire(BypassTranslator::class),
Renderer::class => new Autowire(InterpolationRenderer::class),
ResultFilter::class => new Instantiator(OnlyFailedChildrenResultFilter::class),
'respect.validation.formatter.message' => new Autowire(FirstResultStringFormatter::class),
'respect.validation.formatter.full_message' => new Autowire(NestedListStringFormatter::class),
'respect.validation.formatter.messages' => new Autowire(NestedArrayFormatter::class),
'respect.validation.ignored_backtrace_paths' => [__DIR__ . '/ValidatorBuilder.php'],
'respect.validation.rule_factory.namespaces' => ['Respect\\Validation\\Validators'],
ValidatorFactory::class => factory(static function (Container $container) {
Resolver::class => static fn(Container $container) => new ContainerResolver($container),
ValidatorFactory::class => static function (Container $container) {
$lookup = new NamespaceLookup(
Comment thread
henriquemoody marked this conversation as resolved.
new Ucfirst(),
Validator::class,
...$container->get('respect.validation.rule_factory.namespaces'),
);

return new FluentValidatorFactory(
new ComposingLookup(
new NamespaceLookup(
new Ucfirst(),
Validator::class,
...$container->get('respect.validation.rule_factory.namespaces'),
),
new ComposableMap(
PrefixConstants::COMPOSABLE,
PrefixConstants::COMPOSABLE_WITH_ARGUMENT,
),
new AutowiringLookup(
$lookup,
new ComposableMap(PrefixConstants::COMPOSABLE, PrefixConstants::COMPOSABLE_WITH_ARGUMENT),
$container->get(Resolver::class),
),
);
}),
Quoter::class => create(CodeQuoter::class)->constructor(120),
Handler::class => factory(static function (Container $container) {
},
Quoter::class => new Instantiator(CodeQuoter::class, ['maximumLength' => 120]),
Handler::class => static function (Container $container) {
$handler = CompositeHandler::create();
$handler->prependHandler(new PathHandler($container->get(Quoter::class)));
$handler->prependHandler(new NameHandler());
$handler->prependHandler(new ResultHandler($handler));

return $handler;
}),
PlaceholderFormatter::class => factory(static fn(Container $container) => new PlaceholderFormatter(
},
PlaceholderFormatter::class => static fn(Container $container) => new PlaceholderFormatter(
[],
$container->get(Modifier::class),
)),
Stringifier::class => factory(static fn(Container $container) => new HandlerStringifier(
),
Stringifier::class => static fn(Container $container) => new HandlerStringifier(
$container->get(Handler::class),
new DumpStringifier(),
)),
Modifier::class => factory(static fn(Container $container) => new TransModifier(
),
Modifier::class => static fn(Container $container) => new TransModifier(
new ListModifier(
new QuoteModifier(
new RawModifier(
Expand All @@ -112,16 +122,16 @@ public static function createContainer(array $definitions = []): Container
$container->get(TranslatorInterface::class),
),
$container->get(TranslatorInterface::class),
)),
ValidatorBuilder::class => factory(static fn(Container $container) => new ValidatorBuilder(
),
ValidatorBuilder::class => static fn(Container $container) => new ValidatorBuilder(
$container->get(ValidatorFactory::class),
$container->get(Renderer::class),
$container->get('respect.validation.formatter.message'),
$container->get('respect.validation.formatter.full_message'),
$container->get('respect.validation.formatter.messages'),
$container->get(ResultFilter::class),
$container->get('respect.validation.ignored_backtrace_paths'),
)),
),
]);
}

Expand Down
5 changes: 5 additions & 0 deletions src/NamespacedValidatorFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
use function trim;
use function ucfirst;

/**
* @deprecated This has been superseeded by Respect\Fluent
*
* @see \Respect\Validation\FluentValidatorFactory
*/
final readonly class NamespacedValidatorFactory implements ValidatorFactory
Comment thread
henriquemoody marked this conversation as resolved.
Comment thread
henriquemoody marked this conversation as resolved.
{
/** @param array<int, string> $rulesNamespaces */
Expand Down
32 changes: 29 additions & 3 deletions src/Validators/Attributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use ReflectionProperty;
use ReflectionUnionType;
use Respect\Fluent\Attributes\Composable;
use Respect\Parameter\Resolver;
use Respect\Validation\Id;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
Expand All @@ -43,6 +44,10 @@ final class Attributes implements Validator
/** @var array<int, true> */
private array $visited = [];

public function __construct(private readonly Resolver|null $resolver = null)
{
}

public function evaluate(mixed $input): Result
{
$id = new Id('attributes');
Expand Down Expand Up @@ -73,7 +78,7 @@ private function getClassValidators(ReflectionObject $reflection): array
$validators = [];
while ($reflection instanceof ReflectionClass) {
foreach ($reflection->getAttributes(Validator::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
$validators[] = $attribute->newInstance();
$validators[] = $this->instantiateAttribute($attribute);
}

$reflection = $reflection->getParentClass();
Expand Down Expand Up @@ -107,8 +112,13 @@ private function getPropertyInnerValidators(ReflectionProperty $property): array
$propertyValidators = [];
$hasExplicitAttributes = false;
foreach ($property->getAttributes(Validator::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
$propertyValidator = $attribute->getName() === self::class ? $this : $attribute->newInstance();
$hasExplicitAttributes = $propertyValidator === $this;
if ($attribute->getName() === self::class) {
$propertyValidator = $this;
} else {
$propertyValidator = $this->instantiateAttribute($attribute);
}

$hasExplicitAttributes = $hasExplicitAttributes || $propertyValidator === $this;
$propertyValidators[] = $propertyValidator;
}

Expand Down Expand Up @@ -156,4 +166,20 @@ private function getProperties(ReflectionObject $reflection): array

return $properties;
}

/** @param ReflectionAttribute<Validator> $attribute */
private function instantiateAttribute(ReflectionAttribute $attribute): Validator
{
if ($this->resolver === null) {
return $attribute->newInstance();
}

$reflection = new ReflectionClass($attribute->getName());
$constructor = $reflection->getConstructor();
if ($constructor === null) {
return $attribute->newInstance();
}

return $reflection->newInstanceArgs($this->resolver->resolve($constructor, $attribute->getArguments()));
}
}
Loading
Loading