Initial commit

This commit is contained in:
Matias Navarro Carter
2018-08-08 02:51:48 -04:00
commit 05871cb847
16 changed files with 675 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
<?php
namespace MNC\ChileanRut\Bridge\Doctrine\DBAL\Types;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\StringType;
use MNC\ChileanRut\Rut\Rut;
/**
* Class RutType
* @author Matías Navarro Carter <mnavarro@option.cl>
*/
class RutType extends StringType
{
public const NAME = 'rut';
/**
* @return string
*/
public function getName(): string
{
return self::NAME;
}
/**
* @param mixed $value
* @param AbstractPlatform $platform
* @return mixed
* @throws ConversionException
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if (null === $value) {
return $value;
}
if ($value instanceof Rut) {
return parent::convertToDatabaseValue($value->format(), $platform);
}
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'Rut']);
}
/**
* @param mixed $value
* @param AbstractPlatform $platform
* @return mixed
*/
public function convertToPHPValue($value, AbstractPlatform $platform)
{
$value = parent::convertToPHPValue($value, $platform);
if ($value === null || $value instanceof Rut) {
return $value;
}
return new Rut($value);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace MNC\ChileanRut\Bridge\Symfony\Form;
use MNC\ChileanRut\Rut\Rut;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Class RutType
* @package MNC\ChileanRut\Bridge\Symfony\Form
* @author Matías Navarro Carter <mnavarro@option.cl>
*/
class RutType extends TextType
{
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Rut::class,
]);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace MNC\ChileanRut\Bridge\Symfony\Validator;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
*/
class IsValidRut extends Constraint
{
public $message = 'The rut "{{value}}" is not valid.';
}

View File

@@ -0,0 +1,52 @@
<?php
namespace MNC\ChileanRut\Bridge\Symfony\Validator;
use MNC\ChileanRut\Exception\InvalidRutException;
use MNC\ChileanRut\Rut\Rut;
use MNC\ChileanRut\Validator\RutValidator;
use MNC\ChileanRut\Validator\SimpleRutValidator;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Class IsValidRutValidator
* @package MNC\ChileanRut\Bridge\Symfony\Validator
* @author Matías Navarro Carter <mnavarro@option.cl>
*/
class IsValidRutValidator extends ConstraintValidator
{
/**
* @var RutValidator
*/
private $validator;
public function __construct(RutValidator $validator = null)
{
$this->validator = $validator ?? new SimpleRutValidator();
}
/**
* @param mixed $value
* @param Constraint $constraint
*/
public function validate($value, Constraint $constraint): void
{
if (null === $value || '' === $value) {
return;
}
if (!$value instanceof Rut) {
throw new UnexpectedTypeException($value, Rut::class);
}
try {
$this->validator->validate($value);
} catch (InvalidRutException $exception) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $value->format(Rut::FORMAT_CLEAR))
->addViolation();
}
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace MNC\ChileanRut\Exception;
use MNC\ChileanRut\Rut\Rut;
/**
* Class InvalidRutException
* @package MNC\ChileanRut\Rut
* @author Matías Navarro Carter <mnavarro@option.cl>
*/
class InvalidRutException extends \LogicException
{
/**
* @var Rut
*/
private $rut;
/**
* InvalidRutException constructor.
* @param Rut $rut
*/
public function __construct(Rut $rut)
{
$message = sprintf('Rut %s is not a valid rut.', $rut->format(Rut::FORMAT_READABLE));
$this->rut = $rut;
parent::__construct($message);
}
/**
* @return Rut
*/
public function getRut(): Rut
{
return $this->rut;
}
}

131
src/Rut/Rut.php Normal file
View File

@@ -0,0 +1,131 @@
<?php
namespace MNC\ChileanRut\Rut;
use MNC\ChileanRut\Validator\RutValidator;
/**
* Class Rut
* @author Matías Navarro Carter <mnavarro@option.cl>
*/
class Rut
{
public const FORMAT_HYPHENED = 0; // 14533535-5
public const FORMAT_CLEAR = 1; // 145335355
public const FORMAT_READABLE = 2; // 14.533.535-5
/**
* @var string
*/
private $value;
/**
* @var string
*/
private $dv;
/**
* Rut constructor.
* @param string $rut
* @param RutValidator|null $validator if provided validates the Rut.
*/
public function __construct(string $rut, RutValidator $validator = null)
{
$sanitized = $this->sanitize($rut);
$this->value = substr($sanitized, 0, -1);
$this->dv = $sanitized[\strlen($sanitized) - 1];
if (null !== $validator) {
$validator->validate($this);
}
}
/**
* @param string $correlative
* @param string $verifierDigit
* @return Rut
*/
public static function fromParts(string $correlative, string $verifierDigit): Rut
{
return new self($correlative.$verifierDigit);
}
/**
* @param string $rut
* @return Rut
*/
public static function fromString(string $rut): Rut
{
return new self($rut);
}
/**
* @param Rut $rut
* @return bool
*/
public function isEqualTo(Rut $rut): bool
{
return $this->format() === $rut->format();
}
/**
* @param string $value
* @return string
*/
private function sanitize(string $value): string
{
$value = trim($value);
$value = strtoupper($value);
return str_replace(['.', ',', '-'], '', $value);
}
/**
* @param int $format One of the FORMAT_ constants.
* @return string
*/
public function format(int $format = 0): string
{
switch ($format) {
case self::FORMAT_HYPHENED:
return $this->value . '-' . $this->dv;
break;
case self::FORMAT_CLEAR:
return $this->value . $this->dv;
break;
case self::FORMAT_READABLE:
return sprintf('%s-%s', number_format($this->value, 0, '', '.'), $this->dv);
break;
default:
throw new \InvalidArgumentException(
sprintf(
'Argument provided for %s method of class %s is invalid.',
__METHOD__,
__CLASS__
)
);
}
}
/**
* @return string
*/
public function getCorrelative(): string
{
return $this->value;
}
/**
* @return string
*/
public function getVerifierDigit(): string
{
return $this->dv;
}
/**
* @return string
*/
public function __toString(): string
{
return $this->format(self::FORMAT_READABLE);
}
}

56
src/Util/Correlative.php Normal file
View File

@@ -0,0 +1,56 @@
<?php
namespace MNC\ChileanRut\Util;
use MNC\ChileanRut\Rut\Rut;
/**
* This class provides utils for a Rut correlative.
*
* @author Matías Navarro Carter <mnavarro@option.cl>
*/
class Correlative
{
/**
* Finds the verifier digit of a correlative.
*
* @param string $correlative
* @return string
*/
public static function findVerifierDigit(string $correlative): string
{
$x = 2;
$s = 0;
for ($i = \strlen($correlative) - 1; $i >= 0; $i--) {
if ($x > 7) {
$x = 2;
}
$s += $correlative[$i] * $x;
$x++;
}
$dv = 11 - ($s % 11);
if ($dv === 10) {
$dv = 'K';
}
if ($dv === 11) {
$dv = '0';
}
return (string) $dv;
}
/**
* Instantiates a valid Rut object just providing a correlative.
*
* @param string $correlative
* @return Rut
*/
public static function createValidRutOnlyFromCorrelative(string $correlative): Rut
{
return Rut::fromParts($correlative, static::findVerifierDigit($correlative));
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace MNC\ChileanRut\Validator;
use MNC\ChileanRut\Exception\InvalidRutException;
use MNC\ChileanRut\Rut\Rut;
/**
* A ChainRutValidator
*
* Use this implementation when you want to validate a Rut against multiple
* validators. Add the validators in order by calling addValidator().
*
* @author Matías Navarro Carter <mnavarro@option.cl>
*/
class ChainRutValidator implements RutValidator
{
/**
* @var RutValidator[]
*/
private $validators;
/**
* ChainRutValidator constructor.
*/
public function __construct()
{
$this->validators = [];
}
/**
* @param RutValidator $validator
* @return ChainRutValidator
*/
public function addValidator(RutValidator $validator): ChainRutValidator
{
$this->validators[] = $validator;
return $this;
}
/**
* @param Rut $rut
* @throws InvalidRutException on invalid Rut.
*/
public function validate(Rut $rut): void
{
foreach ($this->validators as $validator) {
$validator->validate($rut);
}
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace MNC\ChileanRut\Validator;
use MNC\ChileanRut\Exception\InvalidRutException;
use MNC\ChileanRut\Rut\Rut;
/**
* This is the base contract for a Rut validator.
*
* You can implement any logic here that you can use to validate a Rut.
* For example, the SimpleRutValidator only validates that a Rut is algorithmically
* correct, but not that it actually exists.
*
* You could create a HTTPRutValidator that performs a request to validate that a
* Rut exists against a Rest Api or a third party service.
*
* @package MNC\ChileanRut\Validator
* @author Matías Navarro Carter <mnavarro@option.cl>
*/
interface RutValidator
{
/**
* Validates a Rut.
*
* The implementation MUST throw an InvalidRutException if validation fails.
*
* The different clients CAN catch that exception and handle the validation
* error according to their business rules.
*
* @param Rut $rut
* @throws InvalidRutException on invalid Rut.
*/
public function validate(Rut $rut): void;
}

View File

@@ -0,0 +1,27 @@
<?php
namespace MNC\ChileanRut\Validator;
use MNC\ChileanRut\Exception\InvalidRutException;
use MNC\ChileanRut\Rut\Rut;
use MNC\ChileanRut\Util\Correlative;
/**
* Validates the Rut using the Module 11 algorithm.
*
* @author Matías Navarro Carter <mnavarro@option.cl>
*/
class SimpleRutValidator implements RutValidator
{
/**
* @param Rut $rut
*/
public function validate(Rut $rut): void
{
$digit = Correlative::findVerifierDigit($rut->getCorrelative());
if ($digit !== $rut->getVerifierDigit()) {
throw new InvalidRutException($rut);
}
}
}