<commit_before><?php

declare(strict_types=1);

namespace Chubbyphp\Validation;

use Chubbyphp\Model\ModelInterface;
use Chubbyphp\Translation\NullTranslator;
use Chubbyphp\Translation\TranslatorInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Respect\Validation\Exceptions\ValidationException;
use Respect\Validation\Rules\AbstractRule;
use Respect\Validation\Validator as v;

final class Validator implements ValidatorInterface
{
    /**
     * @var RequirementInterface[]|array
     */
    private $requirements = [];

    /**
     * @var TranslatorInterface
     */
    private $translator;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @param array                $requirements
     * @param TranslatorInterface  $translator
     * @param LoggerInterface|null $logger
     */
    public function __construct(
        array $requirements = [],
        TranslatorInterface $translator = null,
        LoggerInterface $logger = null
    ) {
        foreach ($requirements as $requirement) {
            $this->addRequirement($requirement);
        }
        $this->translator = $translator ?? new NullTranslator();
        $this->logger = $logger ?? new NullLogger();
    }

    /**
     * @param RequirementInterface $requirement
     */
    private function addRequirement(RequirementInterface $requirement)
    {
        if (!isset($this->requirements[$requirement->provides()])) {
            $this->requirements[$requirement->provides()] = [];
        }

        $this->requirements[$requirement->provides()][] = $requirement;
    }

    /**
     * @param ValidatableModelInterface $model
     * @param string                    $locale
     *
     * @return array
     */
    public function validateModel(ValidatableModelInterface $model, string $locale = 'de'): array
    {
        $errorMessagesFromProperties = $this->assertModelProperties($model, $locale);
        $errorMessagesFromModel = $this->assertValidateModel($model, $locale);

        return array_merge_recursive($errorMessagesFromProperties, $errorMessagesFromModel);
    }

    /**
     * @param ValidatableModelInterface $model
     * @param string                    $locale
     *
     * @return array
     */
    private function assertModelProperties(ValidatableModelInterface $model, string $locale): array
    {
        $errorMessages = [];

        $reflectionClass = new \ReflectionObject($model);
        foreach ($model->getPropertyValidators() as $property => $validator) {
            $value = $this->getPropertyValue($reflectionClass, $model, $property);
            if ([] !== $fieldErrorMessages = $this->assert($validator, $property, $value, $locale)) {
                $errorMessages[$property] = $fieldErrorMessages;
            }
        }

        return $errorMessages;
    }

    /**
     * @param \ReflectionObject $reflection
     * @param ModelInterface    $model
     * @param string            $property
     *
     * @return mixed
     */
    private function getPropertyValue(\ReflectionObject $reflection, ModelInterface $model, string $property)
    {
        $reflectionProperty = $reflection->getProperty($property);
        $reflectionProperty->setAccessible(true);

        return $reflectionProperty->getValue($model);
    }

    /**
     * @param ValidatableModelInterface $model
     * @param string                    $locale
     *
     * @return array
     */
    private function assertValidateModel(ValidatableModelInterface $model, string $locale): array
    {
        if (null === $modelValidator = $model->getModelValidator()) {
            return [];
        }

        $exceptions = [];

        foreach ($modelValidator->getRules() as $rule) {
            $this->setRequirementsPerRule($rule, $model);

            try {
                $rule->assert($model);
            } catch (ValidationException $exception) {
                $exceptions[] = $exception;
            }
        }

        return $this->getModelErrorMessagesByExceptions($exceptions, $locale);
    }

    private function getModelErrorMessagesByExceptions(array $exceptions, string $locale)
    {
        $errorMessages = [];
        foreach ($exceptions as $exception) {
            /** @var ValidationException $exception */
            $properties = $exception->hasParam('properties') ? $exception->getParam('properties') : ['__model'];
            foreach ($properties as $property) {
                if (!isset($errorMessages[$property])) {
                    $errorMessages[$property] = [];
                }
                $errorMessages[$property][] = $this->getMessageByException($exception, $property, '', $locale);
            }
        }
        return $errorMessages;
    }

    /**
     * @param array  $data
     * @param array  $validators
     * @param string $locale
     *
     * @return array
     */
    public function validateArray(array $data, array $validators, string $locale = 'de'): array
    {
        $errorMessages = [];
        foreach ($validators as $key => $validator) {
            $value = $data[$key] ?? null;
            if ([] !== $fieldErrorMessages = $this->assert($validator, $key, $value, $locale)) {
                $errorMessages[$key] = $fieldErrorMessages;
            }
        }

        return $errorMessages;
    }

    /**
     * @param v      $validator
     * @param string $field
     * @param mixed  $value
     * @param string $locale
     *
     * @return array
     */
    private function assert(v $validator, string $field, $value, string $locale)
    {
        $fieldErrorMessages = [];

        foreach ($validator->getRules() as $rule) {
            $this->setRequirementsPerRule($rule, $value);

            try {
                $rule->assert($value);
            } catch (ValidationException $exception) {
                $fieldErrorMessages[] = $this->getMessageByException($exception, $field, $value, $locale);
            }
        }

        return $fieldErrorMessages;
    }

    /**
     * @param AbstractRule $rule
     * @param mixed        $value
     */
    private function setRequirementsPerRule(AbstractRule $rule, $value)
    {
        if (!$rule instanceof LazyRequirementInterface) {
            return;
        }

        foreach ($rule->requires() as $require) {
            $this->setRequirementPerRule($rule, $require, $value);
        }
    }<commit_msg>fix some phpdoc and var names<commit_after><?php

declare(strict_types=1);

namespace Chubbyphp\Validation;

use Chubbyphp\Model\ModelInterface;
use Chubbyphp\Translation\NullTranslator;
use Chubbyphp\Translation\TranslatorInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Respect\Validation\Exceptions\ValidationException;
use Respect\Validation\Rules\AbstractRule;
use Respect\Validation\Validator as v;

final class Validator implements ValidatorInterface
{
    /**
     * @var RequirementInterface[]|array
     */
    private $requirements = [];

    /**
     * @var TranslatorInterface
     */
    private $translator;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @param array                $requirements
     * @param TranslatorInterface  $translator
     * @param LoggerInterface|null $logger
     */
    public function __construct(
        array $requirements = [],
        TranslatorInterface $translator = null,
        LoggerInterface $logger = null
    ) {
        foreach ($requirements as $requirement) {
            $this->addRequirement($requirement);
        }
        $this->translator = $translator ?? new NullTranslator();
        $this->logger = $logger ?? new NullLogger();
    }

    /**
     * @param RequirementInterface $requirement
     */
    private function addRequirement(RequirementInterface $requirement)
    {
        if (!isset($this->requirements[$requirement->provides()])) {
            $this->requirements[$requirement->provides()] = [];
        }

        $this->requirements[$requirement->provides()][] = $requirement;
    }

    /**
     * @param ValidatableModelInterface $model
     * @param string                    $locale
     *
     * @return array
     */
    public function validateModel(ValidatableModelInterface $model, string $locale = 'de'): array
    {
        $errorMessagesFromProperties = $this->assertModelProperties($model, $locale);
        $errorMessagesFromModel = $this->assertValidateModel($model, $locale);

        return array_merge_recursive($errorMessagesFromProperties, $errorMessagesFromModel);
    }

    /**
     * @param ValidatableModelInterface $model
     * @param string                    $locale
     *
     * @return array
     */
    private function assertModelProperties(ValidatableModelInterface $model, string $locale): array
    {
        $reflectionClass = new \ReflectionObject($model);

        $errorMessages = [];
        foreach ($model->getPropertyValidators() as $property => $validator) {
            $value = $this->getPropertyValue($reflectionClass, $model, $property);
            if ([] !== $fieldErrorMessages = $this->assert($validator, $property, $value, $locale)) {
                $errorMessages[$property] = $fieldErrorMessages;
            }
        }

        return $errorMessages;
    }

    /**
     * @param \ReflectionObject $reflection
     * @param ModelInterface    $model
     * @param string            $property
     *
     * @return mixed
     */
    private function getPropertyValue(\ReflectionObject $reflection, ModelInterface $model, string $property)
    {
        $reflectionProperty = $reflection->getProperty($property);
        $reflectionProperty->setAccessible(true);

        return $reflectionProperty->getValue($model);
    }

    /**
     * @param ValidatableModelInterface $model
     * @param string                    $locale
     *
     * @return array
     */
    private function assertValidateModel(ValidatableModelInterface $model, string $locale): array
    {
        if (null === $modelValidator = $model->getModelValidator()) {
            return [];
        }

        $exceptions = [];

        foreach ($modelValidator->getRules() as $rule) {
            $this->setRequirementsPerRule($rule, $model);

            try {
                $rule->assert($model);
            } catch (ValidationException $exception) {
                $exceptions[] = $exception;
            }
        }

        return $this->getModelErrorMessagesByExceptions($exceptions, $locale);
    }

    /**
     * @param array $exceptions
     * @param string $locale
     * @return string[]
     */
    private function getModelErrorMessagesByExceptions(array $exceptions, string $locale)
    {
        $errorMessages = [];
        foreach ($exceptions as $exception) {
            /** @var ValidationException $exception */
            $properties = $exception->hasParam('properties') ? $exception->getParam('properties') : ['__model'];
            foreach ($properties as $property) {
                if (!isset($errorMessages[$property])) {
                    $errorMessages[$property] = [];
                }
                $errorMessages[$property][] = $this->getMessageByException($exception, $property, '', $locale);
            }
        }

        return $errorMessages;
    }

    /**
     * @param array  $data
     * @param array  $validators
     * @param string $locale
     *
     * @return string[]
     */
    public function validateArray(array $data, array $validators, string $locale = 'de'): array
    {
        $errorMessages = [];
        foreach ($validators as $key => $validator) {
            $value = $data[$key] ?? null;
            if ([] !== $fieldErrorMessages = $this->assert($validator, $key, $value, $locale)) {
                $errorMessages[$key] = $fieldErrorMessages;
            }
        }

        return $errorMessages;
    }

    /**
     * @param v      $validator
     * @param string $field
     * @param mixed  $value
     * @param string $locale
     *
     * @return string[]
     */
    private function assert(v $validator, string $field, $value, string $locale)
    {
        $errorMessages = [];
        foreach ($validator->getRules() as $rule) {
            $this->setRequirementsPerRule($rule, $value);

            try {
                $rule->assert($value);
            } catch (ValidationException $exception) {
                $errorMessages[] = $this->getMessageByException($exception, $field, $value, $locale);
            }
        }

        return $errorMessages;
    }

    /**
     * @param AbstractRule $rule
     * @param mixed        $value
     */
    private function setRequirementsPerRule(AbstractRule $rule, $value)
    {
        if (!$rule instanceof LazyRequirementInterface) {
            return;
        }

        foreach ($rule->requires() as $require) {
            $this->setRequirementPerRule($rule, $require, $value);
        }
    }
{'fraction_non_alphanumeric': 0.12459296014886029, 'fraction_numerical': 0.00015506280043417584, 'mean_word_length': 2.2880448636247768, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 71, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '610018', 'n_tokens_mistral': 3399, 'n_tokens_neox': 3340, 'n_words': 701}