Compare commits

..

10 Commits

Author SHA1 Message Date
9a181aafbd chore: workflows and readme
Some checks failed
Chilean Rut CI / CI (push) Failing after 3m36s
2024-03-04 22:12:31 +00:00
8d5b060acf feat: new v4 version (#5) 2024-03-04 22:02:49 +00:00
Carlos Herrera
f1b14d4d40 lint 2021-01-22 20:27:31 +00:00
Carlos Herrera
ef32d3d7c0 adding some tests 2021-01-22 20:27:31 +00:00
Carlos Herrera
bf51a443be Update Rut.php
Arreglar función de calculo de dígito verificador.

Al filtrar y obtener un 0 array_filter enciende que es falso y elimina el digito de secuencia. corrección preguntar vía expresión regular.
2021-01-22 20:27:31 +00:00
peter279k
2cda4451a4 Add PHP8.0 version support 2021-01-22 18:46:21 +00:00
ea092c24d7 Fixed Github Workflow 2020-11-20 12:19:49 +00:00
b4f4fe25b9 [BC] New Version
This version simplifies the api a lot, eliminating unnecesary complexity and reducing the library to a few classes only.
2020-11-20 11:53:29 +00:00
0ad94c4944 Changed repo 2019-06-05 09:09:57 -04:00
4566a4e4cc Changed CI to Gitlab 2018-12-26 09:42:54 -03:00
43 changed files with 5791 additions and 1136 deletions

1
.dev/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
coverage

View File

@@ -0,0 +1,50 @@
FROM alpine:3.18 as base
ARG UID=1000
ARG USER=mnavarro
ARG COMPOSER_VERSION=2.7.0
# Create a development user
RUN adduser $USER --disabled-password --home /$USER --uid $UID
# Install PHP Extensions
RUN apk add --no-cache \
php82-cli \
php82-phar \
php82-mbstring \
php82-json \
php82-zip \
php82-openssl \
php82-dom \
php82-xml \
php82-soap \
php82-session \
php82-xmlwriter \
php82-sockets \
php82-simplexml \
php82-bcmath \
php82-xmlreader \
php82-tokenizer \
php82-iconv \
php82-sodium \
php82-fileinfo \
php82-curl \
php82-ctype \
php82-pcntl \
php82-posix
# Link php82 to php
RUN ln -s /usr/bin/php82 /usr/bin/php
# Add wget to make requests
RUN apk add --no-cache wget
# Download and install composer
RUN wget -O /usr/bin/composer https://github.com/composer/composer/releases/download/$COMPOSER_VERSION/composer.phar && \
chmod +x /usr/bin/composer
FROM base as dev
# Install and Configure XDebug
RUN apk add --no-cache php82-pecl-xdebug
COPY ./xdebug.ini /etc/php82/conf.d/60_xdebug.ini

View File

@@ -0,0 +1,6 @@
zend_extension=xdebug.so
[xdebug]
xdebug.mode=debug,develop,coverage
xdebug.client_host=host.docker.internal
xdebug.output_dir=/castor/context/.dev/debug

7
.dev/init Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
set -e
mkdir -p .dev/coverage
composer install

12
.gitattributes vendored Normal file
View File

@@ -0,0 +1,12 @@
/.idea export-ignore
/vendor export-ignore
/tests export-ignore
/.dev export-ignore
/.php-cs-fixer.dist.php export-ignore
/compose.yml export-ignore
/composer.lock export-ignore
/phpunit.xml export-ignore
/psalm.xml export-ignore
/psalm-baseline.xml export-ignore
/.gitignore export-ignore
/README.md export-ignore

25
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: "Chilean Rut CI"
on:
pull_request:
branches: ['master']
paths:
- '**.php'
- 'composer.json'
- '.github/workflows/ci.yml'
- '.github/workflows/php.yml'
push:
branches: ['master']
paths:
- '**.php'
- 'composer.json'
- '.github/workflows/ci.yml'
- '.github/workflows/php.yml'
jobs:
ci:
name: 'CI'
uses: ./.github/workflows/php.yml
with:
php-version: 8.2
composer-cmd: install --ansi --no-interaction --no-progress --no-suggest --prefer-dist
secrets: inherit

60
.github/workflows/php.yml vendored Normal file
View File

@@ -0,0 +1,60 @@
name: "PHP Checks"
on:
workflow_call:
inputs:
php-version:
description: 'The version of PHP to use'
default: '8.2'
type: string
composer-cmd:
description: 'Command to install dependencies'
type: string
default: 'install --ansi --no-interaction --no-progress --no-suggest --prefer-dist'
jobs:
php-checks:
name: "PHP Checks"
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: "Checkout Code"
uses: "actions/checkout@v2"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "pcov"
php-version: "${{ inputs.php-version }}"
ini-values: memory_limit=-1
tools: composer:v2
- name: Get Composer Cache Directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: "Cache dependencies"
uses: "actions/cache@v2"
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: "${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}"
restore-keys: "${{ runner.os }}-composer-"
- name: "Install dependencies"
run: "composer ${{ inputs.composer-cmd }}"
- name: "Check Code Style"
run: "composer fmt:check"
- name: "Run Psalm"
run: "composer psalm:gh"
- name: "Run Test Suite"
run: "composer test"
- name: Setup Pages
uses: actions/configure-pages@v4
if: ${{ github.ref_name == 'master' }}
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
if: ${{ github.ref_name == 'master' }}
with:
path: '.dev/coverage'
- name: Deploy to GitHub Pages
if: ${{ github.ref_name == 'master' }}
id: deployment
uses: actions/deploy-pages@v4

6
.gitignore vendored
View File

@@ -1,5 +1,3 @@
vendor vendor
composer.lock .env
.idea compose.override.yml
.php_cs.cache
build

37
.php-cs-fixer.dist.php Normal file
View File

@@ -0,0 +1,37 @@
<?php
use PhpCsFixer\Fixer\FunctionNotation\NativeFunctionInvocationFixer;
$header = <<<EOF
@project Chilean RUT
@link https://github.com/mnavarrocarter/chilean-rut
@package castor/log
@author Matias Navarro-Carter mnavarrocarter@gmail.com
@license MIT
@copyright 2024 Matias Navarro-Carter
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
EOF;
return (new PhpCsFixer\Config())
->setCacheFile('/tmp/php-cs-fixer')
->setRiskyAllowed(true)
->setRules([
'@PhpCsFixer' => true,
'declare_strict_types' => true,
'header_comment' => ['header' => $header, 'comment_type' => 'PHPDoc'],
'yoda_style' => false,
'php_unit_internal_class' => false,
'php_unit_test_class_requires_covers' => false,
'native_function_invocation' => [
'include' => [NativeFunctionInvocationFixer::SET_ALL],
'scope' => 'namespaced',
]
])
->setFinder(
PhpCsFixer\Finder::create()
->in(["src", "tests"])
)
;

View File

@@ -1,32 +1,35 @@
<?php <?php
$header = <<<EOF $header = <<<EOF
This file is part of the MNC\ChileanRut library. @project Chilean Rut
@link https://github.com/mnavarrocarter/chilean-rut
@package mnavarrocarter/chilean-rut
@author Matias Navarro-Carter mnavarrocarter@gmail.com
@license MIT
@copyright 2020 Matias Navarro Carter
(c) Matías Navarro Carter <mnavarrocarter@gmail.com>
For the full copyright and license information, please view the LICENSE For the full copyright and license information, please view the LICENSE
file that was distributed with this source code. file that was distributed with this source code.
EOF; EOF;
return PhpCsFixer\Config::create() return PhpCsFixer\Config::create()
->setRiskyAllowed(true)
->setRules([ ->setRules([
'@Symfony' => true, '@Symfony' => true,
'array_syntax' => ['syntax' => 'short'], 'array_syntax' => ['syntax' => 'short'],
'combine_consecutive_unsets' => true, 'declare_strict_types' => true,
'header_comment' => ['header' => $header], 'strict_comparison' => true,
'linebreak_after_opening_tag' => true, 'phpdoc_no_empty_return' => true,
'no_php4_constructor' => true, 'header_comment' => ['header' => $header, 'comment_type' => 'PHPDoc'],
'no_useless_else' => true, 'yoda_style' => [
'ordered_class_elements' => true, 'equal' => false,
'ordered_imports' => true, 'identical' => false,
'php_unit_construct' => true, 'less_and_greater' => false,
'php_unit_strict' => true, 'always_move_variable' => true
'phpdoc_no_empty_return' => false, ],
]) ])
->setUsingCache(true)
->setRiskyAllowed(true)
->setFinder( ->setFinder(
PhpCsFixer\Finder::create() PhpCsFixer\Finder::create()
->in(__DIR__) ->in(__DIR__.'/src')
) )
; ;

View File

@@ -1,30 +0,0 @@
language: php
env:
global:
- CC_TEST_REPORTER_ID=$CC_TEST_REPORTER_ID
dist: precise
php:
- '7.1'
- '7.2'
- nightly
matrix:
allow_failures:
- php: nightly
sudo: false
cache:
directories:
- $HOME/.composer/cache
before_script:
- travis_retry composer self-update
- travis_retry composer install --no-interaction --prefer-dist
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
- chmod +x ./cc-test-reporter
- ./cc-test-reporter before-build
script: composer run test
after_script:
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT

View File

@@ -1,18 +0,0 @@
# CHANGELOG
## v1.0.0 (08.08.2018)
- Create Initial Classes (Rut, Validator, ChainRutValidator)
- Tested Rut
- Tested SimpleRutValidator
- Tested Symfony Form Type
## v2.0.0 (12.10.2018)
This is a major version because api changes and class renaming took place.
Also, all features were properly tested with CI/CD pipelines.
- Changed ChainRutValidator constructor signature. Now uses argument spreading.
- Added tests for ChainRutValidator
- SimpleRutValidator renamed to Module11RutValidator
- Now class Rut always validates itself with the SimpleRutValidator if no validator
is passed. This ensures object consistency.
- Added test coverage reports, code quality and CI testing

View File

@@ -1,4 +1,4 @@
Copyright (c) 2018 Matías Navarro Carter Copyright (c) 2020 Matías Navarro Carter
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

257
README.md
View File

@@ -1,220 +1,117 @@
Rut Chileno Rut Chileno
=========== ===========
[![Build Status](https://travis-ci.org/mnavarrocarter/chilean-rut.svg?branch=master)](https://travis-ci.org/mnavarrocarter/chilean-rut) Esta librería implementa una clase Rut como un sencillo *value object* inmutable.
[![Maintainability](https://api.codeclimate.com/v1/badges/c93bd4d894722c404cfd/maintainability)](https://codeclimate.com/github/mnavarrocarter/chilean-rut/maintainability)
[![Test Coverage](https://api.codeclimate.com/v1/badges/c93bd4d894722c404cfd/test_coverage)](https://codeclimate.com/github/mnavarrocarter/chilean-rut/test_coverage)
[![Latest Stable Version](https://poser.pugx.org/mnavarrocarter/chilean-rut/v/stable.svg)](https://packagist.org/packages/mnavarrocarter/chilean-rut)
[![Latest Unstable Version](https://poser.pugx.org/mnavarrocarter/chilean-rut/v/unstable)](https://packagist.org/packages/mnavarrocarter/chilean-rut)
[![Total Downloads](https://poser.pugx.org/mnavarrocarter/chilean-rut/downloads)](https://packagist.org/packages/mnavarrocarter/chilean-rut)
[![License](https://poser.pugx.org/mnavarrocarter/chilean-rut/license)](https://packagist.org/packages/mnavarrocarter/chilean-rut)
Esta librería implementa una clase Rut como un *value object* inmutable, incluyendo Además, posee dos *types* para `doctrine/dbal`.
una api de validación flexible y extendible.
Además, posee un validador para `symfony/validator`, un *form type* para `symfony/form`
y un *type* para `doctrine/dbal`.
Sólo es compatible con PHP 7.1 o superior.
## Instalación ## Instalación
Esta libería funciona con composer: Esta puede ser instalada mediante composer:
``` ```
composer require mnavarrocarter/chilean-rut composer require mnavarrocarter/chilean-rut
``` ```
## Uso ## Uso
Simplemente instancia una nueva clase con un rut en cualquier formato:
### Parseando un Rut
La clase Rut es capaz de parsear cualquier tipo de rut sin importar el formato usando
el método `Rut::parse()`. Confiadamente, puedes poner el valor directamente de un
formulario web y `parse` se encargará de sanitizar el string y ver si el RUT es válido.
> NOTA: Un Rut se considera válido cuando su dígito verificador es algorítmicamente válido
> para el número. Esta libreria no puede validar que el Rut existe realmente.
```php
<?php
use MNC\Rut;
$rut = Rut::parse('23.546.565-4');
```
### Validando el RUT
> TLDR: Un objeto `Rut` siempre será valido.
Si tu RUT no es válido, el método `parse` lanzara una excepción de tipo
`MNC\Rut\IsInvalid`. Esto es para seguir buenos principios de *objects
calisthenics*: un objeto de valor siempre se crea en un estado válido, y se mantiene
válido a través de todo su ciclo de vida. No se permiten mutaciones que dejen el
objeto en un estado inválido.
Por esta razón el objeto `MNC\Rut\IsInvalid` es completamente inmutable. Esto quiere decir
que una vez creado no puedes cambiar su estado interno: solo puedes leer información.
Estos son los unicos métodos que puedes usar:
```php ```php
<?php <?php
use MNC\ChileanRut\Rut; use MNC\Rut;
$rut = new Rut('23.546.565-4'); $rut = Rut::parse('23.546.565-4');
// Si prefieres, puedes usar el factory method $rut->number; // (int) 23546565
$rut->verifier; // (MNC\Rut\Verifier::Four) 4
$rut = Rut::fromString('23546565-4');
``` ```
Por defecto, la clase Rut se valida usando el `Module11RutValidator` si no se pasa ### Formateando el Rut
un validador personalizado al momento de instanciación. Esto es para asegurar la
integridad del objeto.
Si quieres, por alguna extraña razón, deshacerte de esa validación, puedes crear Existen muchas formas distintas de formatear un RUT y esta librería soporta muchas
un `AlwaysValidRutValidator` implementando la interfaz `RutValidator`. El método de ellas. El método format devuelve un objeto al cual puedes encadenar llamadas para
validate estaría en blanco, lo que haría pasar la validación sin problema. formatear el RUT y extraer su información de diversas maneras.
```php He aquí algunos ejemplos:
<?php
use MNC\ChileanRut\Rut;
use MNC\ChileanRut\Validator\RutValidator;
class AlwaysValidRutValidator implements RutValidator
{
public function validate(Rut $rut) : void
{
// Vacío a propósito
}
}
// Asi, la validación pasa sin problema.
$rut = new Rut('23.546.565-4', new AlwaysValidRutValidator());
```
### Validación Personalizada de Rut
El `Module11RutValidator` no es más que la implementación del validador clásico de Rut,
el algoritmo de módulo 11. Esto verifica que un Rut es algoritmicamente correcto, pero
no valida que es real.
Por ello, proveemos la interfaz `RutValidator`. Con ella, puedes crear tus propias
reglas de validación, como llamar a un web service o consultar una base de datos
para verificar si un Rut es real o no. Te recomiendo mirar la interfaz para
implementarla correctamente.
De todas formas, aquí hay un ejemplo que va a buscar un rut a un web service.
```php ```php
<?php <?php
use MNC\ChileanRut\Validator\RutValidator; use MNC\Rut;
use MNC\ChileanRut\Rut;
use MNC\ChileanRut\Exception\InvalidRutException;
use App\Rut\WebServiceRutChecker;
class MyCustomRutValidator implements RutValidator $rut = Rut::parse('23.546.565-4');
{
private $rutChecker;
/**
* MyCustomRutValidator constructor.
* @param WebServiceRutChecker $rutChecker
*/
public function __construct(WebServiceRutChecker $rutChecker)
{
$this->rutChecker = $rutChecker;
}
/**
* @param Rut $rut
*/
public function validate(Rut $rut) : void
{
// Por debajo, esta clase ficticia haría una llamada a un web service preguntando
// si el Rut existe.
if ($this->rutChecker->doesRutExist($rut->format())) {
return;
}
throw new InvalidRutException($rut, 'This rut does not exist');
}
}
echo $rut->toString(); // 235465654
echo $rut->toSimple(); // 23546565-4
echo $rut->toHuman(); // 23.546.565-4
echo $rut->last(4); // 6565
echo $rut->last(4, pad: '*'); // ****6565
echo $rut->first(4); // 2354
echo $rut->first(4, pad: '*'); // 2354****
``` ```
> NOTA: La implementación de cualquier validador DEBE arrojar un InvalidRutException cuando ## Integraciones con Librerías de Terceros
el Rut no es válido. De lo contrario, el Rut se toma como válido.
### Usando múltiples validadores
Proveemos un `ChainRutValidator` que puedes usar para validar un rut contra múltiples
validadores. Esto permite ejecutar cadenas de validación, como ver primero si un rut es
válido algorítmicamente antes de verificarlo contra un web service.
Usarlo es simple:
```php
<?php
use MNC\ChileanRut\Rut;
use MNC\ChileanRut\Validator\ChainRutValidator;
use MNC\ChileanRut\Validator\SimpleRutValidator;
use App\Rut\DatabaseRutValidator;
$chainValidator = new ChainRutValidator(
new SimpleRutValidator(),
new DatabaseRutValidator()
);
$rut = new Rut('14.245.245-2');
$chainValidator->validate($rut);
```
### Formateando Ruts a String
Una vez creado el objeto Rut, puedes formatearlo a string en el formato que tu quieras.
Esto se hace a través del método format y cómo parámetro acepta el valor
de una de las constantes `FORMAT_` de la clase Rut.
```php
<?php
use MNC\ChileanRut\Rut;
$rut = new Rut('34244223-4');
echo $rut->format(Rut::FORMAT_CLEAR); // Va a imprimir 342442234
echo $rut->format(Rut::FORMAT_READABLE); // Va a imprimir 34.244.223-4
echo $rut->format(Rut::FORMAT_HYPHENED); // Va a imprimir 34244223-4
echo $rut->format(Rut::FORMAT_HIDDEN); // Va a imprimir 34.***.***-4
```
### Utilidades
Esta librería provee una clase llamada `CorrelativeUtils` que tiene algunas utilidades
interesantes. Posee tres métodos:
```php
<?php
use MNC\ChileanRut\Util\CorrelativeUtils;
// Este método devuelve el digito verificador de un correlativo.
CorrelativeUtils::findVerifierDigit('34525252');
// Este método devuelve una instancia de Rut válida, sólo con el correlativo.
CorrelativeUtils::createValidRutOnlyFromCorrelative('34525252');
// Este método devuelve instancia de Rut autogenerada algoritmicamente válida.
CorrelativeUtils::autoGenerateValidRut();
```
## Integraciones con Liberías de Terceros
### Doctrine DBAL ### Doctrine DBAL
Esta libería provee un custom type para doctrine llamado `RutType`. Puedes registrarla Esta librería provee dos [*Custom Types*](https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/cookbook/custom-mapping-types.html)
en el Dbal para usarla en tus mappings de doctrine y automáticamente mappear tu para Doctrine, con el objetivo de que puedas mapear tus objetos `Rut` fácilmente
el valor de tu db a un objeto rut. a una base de datos relacional.
### Symfony Validator `MNC\Rut\Doctrine\RutType` mapea tu RUT a una columna VARCHAR.
Además, esta libería cuenta con un validador para Symfony Validator, que te El string se guarda con puntos y guion. Ex: `16.894.365-2`. Es una forma no tan
permite beneficiarte de las anotaciones del componente de validación de Symfony. eficiente de guardar los RUTS (en términos de espacio), pero ayuda mucho cuando
Como dependencia opcional necesita una instancia de `RutValidator`. Si ninguna es proveída, se visualiza o exporta la base de datos a otras fuentes.
se utiliza el `SimpleRutValidator`. Solo puedes usar el validador contra una instancia de `Rut`.
### Symfony Form Type `MNC\Rut\Doctrine\NumericRutType` mapea tu RUT a una columna INTEGER.
Por último, esta libería cuenta con un Symfony Form Type que puedes añadir en tus El número se guarda sin dígito verificador y es recalculado cuando la columna
formularios HTML, para que puedas autoinstanciar la clase y poner lógica de es transformada a un valor PHP. Esta forma de guardar ruts es muy eficiente (en
validación en ella sin problema, y añadirla a tus otros tipos. términos de espacio), pero cuesta comparar y leer los números si visualizas o
exportas los registros en la base de datos.
Por supuesto, puedes elegir el `Type` que más se ajuste a tus necesidades.
## FAQ ## FAQ
### ¿Cómo nació y por qué esta librería? ### ¿Por qué esta librería?
Esta libería nace de la necesidad de estandarizar una clase Rut común para todos mis proyectos Esta librería nace de la necesidad de estandarizar una clase Rut común para todos
PHP. mis proyectos PHP.
Si bien es cierto, hay muchas liberías con implementaciones de Rut chilenos en PHP,
Si bien es cierto, hay muchas librerías con implementaciones de Rut chilenos en PHP,
muchas de ellas tienen notorias deficiencias: muchas de ellas tienen notorias deficiencias:
1. No están testeadas unitariamente, 1. No están testeadas unitariamente.
2. No separan bien responsabilidades, como la lógica de validación con la de instanciación. 2. No estan tipadas apropiadamente
3. No proveen validación extensible por medio de interfaces, limitando la validación 3. No tienen un buen diseño y sus apis tienen efectos secundarios.
solo a ser algorítmica. 4. Están acopladas a un framework (Laravel Rut y otras hierbas)
4. Están acopladas a un framework 5. No proveen herramientas ni integraciones con librerías de terceros, como Doctrine.
5. No proveen herramientas ni integraciones con librerías de terceros.
### ¿Por qué PHP 7.1?
El fin del soporte de PHP 5.6 será a fines de 2018. PHP 7.1 es una de las últimas
versiones estables, y me beneficio mucho de su sistema de tipado estricto en esta libería.

27
compose.yml Normal file
View File

@@ -0,0 +1,27 @@
name: "mnavarro"
services:
chilean-rut: &php
image: mnavarro/chilean-rut:dev
build:
context: .dev/docker/php
dockerfile: Dockerfile
target: dev
args:
UID: ${UID:-1000}
user: ${UID:-1000}
restart: unless-stopped
depends_on:
chilean-rut-init:
condition: service_completed_successfully
working_dir: /mnavarro/chilean-rut
volumes:
- ./:/mnavarro/chilean-rut
command: ["php", "-S", "0.0.0.0:8000", "-t", ".dev/coverage"]
chilean-rut-init:
<<: *php
depends_on: []
restart: no
command: [".dev/init"]

View File

@@ -3,36 +3,46 @@
"description": "PHP Rut Value Object with validation utilities, doctrine type, and other cool features.", "description": "PHP Rut Value Object with validation utilities, doctrine type, and other cool features.",
"type": "library", "type": "library",
"license": "MIT", "license": "MIT",
"minimum-stability": "dev",
"prefer-stable": true,
"authors": [ "authors": [
{ {
"name": "Matias Navarro Carter", "name": "Matias Navarro-Carter",
"email": "mnavarro@option.cl" "email": "mnavarrocarter@gmail.com",
"role": "Lead Maintainer"
} }
], ],
"minimum-stability": "stable",
"require": {
"php": "^7.1"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.12",
"phpunit/phpunit": "^7.3",
"doctrine/dbal": "^2.5",
"symfony/form": "^3.4|^4.0",
"symfony/validator": "^3.4|^4.0",
"symfony/var-dumper": "^4.1"
},
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"MNC\\ChileanRut\\": "src/" "MNC\\": "src"
} }
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {
"MNC\\ChileanRut\\Tests\\": "tests" "MNC\\": "tests"
} }
}, },
"require": {
"php": ">=8.2"
},
"require-dev": {
"phpunit/phpunit": "^10.5",
"friendsofphp/php-cs-fixer": "^3.49",
"vimeo/psalm": "^5.22",
"doctrine/dbal": "^2.10.1|^3.0"
},
"scripts": { "scripts": {
"test": "@php vendor/bin/phpunit --verbose --coverage-text --coverage-clover build/logs/clover.xml", "pr": ["@fmt", "@psalm", "@test"],
"style": "@php vendor/bin/php-cs-fixer fix" "ci": ["@fmt:check", "@psalm:gh", "@test"],
"fmt": "php-cs-fixer fix --diff --ansi",
"fmt:check": "php-cs-fixer fix --dry-run --diff --ansi",
"test": ["phpunit --colors"],
"test:unit": "phpunit --colors --exclude-group=integration --exclude-group=e2e",
"test:e2e": "phpunit --colors --group=e2e",
"test:integration": "phpunit --colors --group=integration",
"psalm": "psalm --no-cache --threads=5 --use-baseline",
"psalm:gh": "psalm --no-cache --threads=5 --long-progress --output-format=github --use-baseline",
"psalm:fix": "psalm --update-baseline",
"psalm:allow": "psalm --set-baseline=psalm-baseline.xml"
} }
} }

4635
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

29
phpunit.xml Normal file
View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
bootstrap="vendor/autoload.php"
executionOrder="depends,defects"
requireCoverageMetadata="true"
beStrictAboutCoverageMetadata="true"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
cacheResultFile="/tmp/phpunit.result.cache"
failOnWarning="true">
<testsuites>
<testsuite name="main">
<directory>tests</directory>
</testsuite>
</testsuites>
<source restrictDeprecations="true" restrictNotices="true" restrictWarnings="true">
<include>
<directory>src</directory>
</include>
</source>
<coverage>
<report>
<html outputDirectory=".dev/coverage"/>
</report>
</coverage>
</phpunit>

View File

@@ -1,25 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" backupStaticAttributes="false"
<phpunit backupGlobals="false" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true"
backupStaticAttributes="false" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false"
colors="true" bootstrap="vendor/autoload.php" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
convertErrorsToExceptions="true" <coverage>
convertNoticesToExceptions="true" <include>
convertWarningsToExceptions="true" <directory>./src</directory>
processIsolation="false" </include>
stopOnFailure="false" </coverage>
bootstrap="vendor/autoload.php"
>
<testsuites> <testsuites>
<testsuite name="Test Suite"> <testsuite name="Test Suite">
<directory suffix="Test.php">./tests</directory> <directory suffix="Test.php">./tests</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
</phpunit>
<filter>
<whitelist>
<directory>./src</directory>
</whitelist>
</filter>
</phpunit>

8
psalm-baseline.xml Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="5.22.2@d768d914152dbbf3486c36398802f74e80cfde48">
<file src="src/Rut/Doctrine/NumericRutType.php">
<ImplementedReturnTypeMismatch>
<code><![CDATA[?Rut]]></code>
</ImplementedReturnTypeMismatch>
</file>
</files>

17
psalm.xml Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<psalm
errorLevel="1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
findUnusedBaselineEntry="true"
findUnusedCode="false"
errorBaseline="psalm-baseline.xml"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>

54
psalm.xml.dist Normal file
View File

@@ -0,0 +1,54 @@
<?xml version="1.0"?>
<psalm
totallyTyped="false"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<issueHandlers>
<LessSpecificReturnType errorLevel="info" />
<!-- level 3 issues - slightly lazy code writing, but provably low false-negatives -->
<DeprecatedMethod errorLevel="info" />
<DeprecatedProperty errorLevel="info" />
<DeprecatedClass errorLevel="info" />
<DeprecatedConstant errorLevel="info" />
<DeprecatedFunction errorLevel="info" />
<DeprecatedInterface errorLevel="info" />
<DeprecatedTrait errorLevel="info" />
<InternalMethod errorLevel="info" />
<InternalProperty errorLevel="info" />
<InternalClass errorLevel="info" />
<MissingClosureReturnType errorLevel="info" />
<MissingReturnType errorLevel="info" />
<MissingPropertyType errorLevel="info" />
<InvalidDocblock errorLevel="info" />
<PropertyNotSetInConstructor errorLevel="info" />
<MissingConstructor errorLevel="info" />
<MissingClosureParamType errorLevel="info" />
<MissingParamType errorLevel="info" />
<RedundantCondition errorLevel="info" />
<DocblockTypeContradiction errorLevel="info" />
<RedundantConditionGivenDocblockType errorLevel="info" />
<UnresolvableInclude errorLevel="info" />
<RawObjectIteration errorLevel="info" />
<InvalidStringClass errorLevel="info" />
</issueHandlers>
</psalm>

View File

@@ -1,70 +0,0 @@
<?php
/*
* This file is part of the MNC\ChileanRut library.
*
* (c) Matías Navarro Carter <mnavarrocarter@gmail.com>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
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;
/**
* 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 $value->format(Rut::FORMAT_HYPHENED);
}
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', Rut::class]);
}
/**
* @param mixed $value
* @param AbstractPlatform $platform
*
* @return mixed
*/
public function convertToPHPValue($value, AbstractPlatform $platform)
{
if (null === $value || $value instanceof Rut) {
return $value;
}
return new Rut($value);
}
}

View File

@@ -1,75 +0,0 @@
<?php
/*
* This file is part of the MNC\ChileanRut library.
*
* (c) Matías Navarro Carter <mnavarrocarter@gmail.com>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MNC\ChileanRut\Bridge\Symfony\Form;
use MNC\ChileanRut\Rut;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Class RutType.
*
* @author Matías Navarro Carter <mnavarro@option.cl>
*/
class RutType extends AbstractType implements DataTransformerInterface
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->addModelTransformer($this);
}
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'compound' => false,
'data_class' => Rut::class,
'empty_data' => function (FormInterface $form) {
return new Rut($form->getData());
},
]);
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix(): ?string
{
return 'rut';
}
/**
* @param mixed $value
*
* @return mixed|string
*/
public function transform($value)
{
if ($value instanceof Rut) {
return $value->format(Rut::FORMAT_READABLE);
}
}
/**
* @param mixed $value
*
* @return mixed|Rut
*/
public function reverseTransform($value)
{
return new Rut((string) $value);
}
}

View File

@@ -1,21 +0,0 @@
<?php
/*
* This file is part of the MNC\ChileanRut library.
*
* (c) Matías Navarro Carter <mnavarrocarter@gmail.com>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MNC\ChileanRut\Bridge\Symfony\Validator;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
*/
class IsValidRut extends Constraint
{
public $message = '"{{value}}" is not a valid Rut.';
}

View File

@@ -1,65 +0,0 @@
<?php
/*
* This file is part of the MNC\ChileanRut library.
*
* (c) Matías Navarro Carter <mnavarrocarter@gmail.com>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MNC\ChileanRut\Bridge\Symfony\Validator;
use MNC\ChileanRut\Exception\InvalidRutException;
use MNC\ChileanRut\Rut;
use MNC\ChileanRut\Validator\Module11RutValidator;
use MNC\ChileanRut\Validator\RutValidator;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Class IsValidRutValidator.
*
* @author Matías Navarro Carter <mnavarro@option.cl>
*/
class IsValidRutValidator extends ConstraintValidator
{
/**
* @var RutValidator
*/
private $validator;
/**
* IsValidRutValidator constructor.
*
* @param RutValidator|null $validator
*/
public function __construct(RutValidator $validator = null)
{
$this->validator = $validator ?? new Module11RutValidator();
}
/**
* @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

@@ -1,49 +0,0 @@
<?php
/*
* This file is part of the MNC\ChileanRut library.
*
* (c) Matías Navarro Carter <mnavarrocarter@gmail.com>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MNC\ChileanRut\Exception;
use MNC\ChileanRut\Rut;
/**
* Class InvalidRutException.
*
* @author Matías Navarro Carter <mnavarro@option.cl>
*/
class InvalidRutException extends \LogicException
{
/**
* @var Rut
*/
private $rut;
/**
* InvalidRutException constructor.
*
* @param Rut $rut
* @param string|null $message
*/
public function __construct(Rut $rut, string $message = null)
{
if (null === $message) {
$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;
}
}

View File

@@ -1,193 +1,175 @@
<?php <?php
/* declare(strict_types=1);
* This file is part of the MNC\ChileanRut library.
/**
* @project Chilean RUT
* @link https://github.com/mnavarrocarter/chilean-rut
* @package castor/log
* @author Matias Navarro-Carter mnavarrocarter@gmail.com
* @license MIT
* @copyright 2024 Matias Navarro-Carter
* *
* (c) Matías Navarro Carter <mnavarrocarter@gmail.com>
* For the full copyright and license information, please view the LICENSE * For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace MNC\ChileanRut; namespace MNC;
use MNC\ChileanRut\Validator\Module11RutValidator; use MNC\Rut\Verifier;
use MNC\ChileanRut\Validator\RutValidator;
/** /**
* Rut represents a the Chilean National ID Number. * Esta clase representa un RUT.
* *
* All residents of Chile are uniquely identified by one of these. * Una vez creado, el RUT es siempre valido
*
* @author Matías Navarro Carter <mnavarro@option.cl>
*/ */
class Rut final readonly class Rut
{ {
public const FORMAT_HYPHENED = 0; // 14533535-5 private const MAX_NUMBER = 999_999_999;
public const FORMAT_CLEAR = 1; // 145335355 private const MIN_NUMBER = 0;
public const FORMAT_READABLE = 2; // 14.533.535-5
public const FORMAT_HIDDEN = 3; // 17.***.***-5 private function __construct(
public int $number,
public Verifier $verifier
) {}
/** /**
* @var string * Parsea un objeto RUT a partir de una cadena de texto.
*/
private $value;
/**
* @var string
*/
private $dv;
/**
* Rut constructor.
* *
* @param string $rut * El RUT DEBE contener el digito verificador.
* @param RutValidator|null $validator if provided validates the Rut *
* Si no se cuenta con el verificador, el metodo create debe ser usado.
*
* @see Rut::create
*
* @throws Rut\IsInvalid si el RUT no es valido
*/ */
public function __construct(string $rut, RutValidator $validator = null) public static function parse(string $rut): Rut
{ {
$sanitized = $this->sanitize($rut); // Remove space, dots and hyphens
$this->value = substr($sanitized, 0, -1); $rut = \str_replace([' ', '.', '-'], '', $rut);
$this->dv = $sanitized[\strlen($sanitized) - 1];
if (!$validator instanceof RutValidator) { return self::create(
$validator = new Module11RutValidator(); (int) \substr($rut, 0, -1),
Verifier::fromString(\substr($rut, -1))
);
}
/**
* Crea un objeto RUT.
*
* El digito verificador es opcional. Cuando es recibido, es validado.
*
* Si el digito verificador no es provisto, es generado automáticamente.
*
* @throws Rut\IsInvalid si el Rut es invalido
*/
public static function create(int $number, ?Verifier $verifier = null): self
{
if ($number < self::MIN_NUMBER) {
throw Rut\IsInvalid::numberTooSmall($number);
} }
$validator->validate($this);
}
/** if ($number > self::MAX_NUMBER) {
* Casts the Rut object into a string. throw Rut\IsInvalid::numberTooBig($number);
*
* @return string
*/
public function __toString(): string
{
return $this->format(self::FORMAT_READABLE);
}
/**
* Creates a new Rut instance from the correlative and the verifier digit.
*
* @param string $correlative
* @param string $verifierDigit
* @param RutValidator|null $validator
*
* @return Rut
*/
public static function fromParts(string $correlative, string $verifierDigit, RutValidator $validator = null): Rut
{
return new self($correlative.$verifierDigit, $validator);
}
/**
* Creates a new instance of Rut from a string.
*
* @param string $rut
* @param RutValidator|null $validator
*
* @return Rut
*/
public static function fromString(string $rut, RutValidator $validator = null): Rut
{
return new self($rut, $validator);
}
/**
* Compares whether a Rut is equal to another or not.
*
* @param Rut $rut
*
* @return bool
*/
public function isEqualTo(Rut $rut): bool
{
return $this->format() === $rut->format();
}
/**
* Formats a Rut to a string.
*
* @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 $this->formatReadable();
break;
case self::FORMAT_HIDDEN:
return $this->formatHidden();
break;
default:
throw new \InvalidArgumentException(
sprintf(
'Argument provided for %s method of class %s is invalid.',
__METHOD__,
__CLASS__
)
);
} }
$computed = self::computeVerifier($number);
if ($verifier === null) {
return new self($number, $computed);
}
if ($computed !== $verifier) {
throw Rut\IsInvalid::rut($number, $verifier);
}
return new self($number, $verifier);
} }
/** /**
* Returns the correlative number of the Rut. * Compara si un RUT es igual a otro o no.
*
* @return string
*/ */
public function getCorrelative(): string public function equals(Rut $rut): bool
{ {
return $this->value; return $this->number === $rut->number;
} }
/** /**
* Returns the verifier digit of the Rut. * Retorna el RUT en formato "12345678K".
*
* @return string
*/ */
public function getVerifierDigit(): string public function toString(): string
{ {
return $this->dv; return $this->number.$this->verifier->toString();
} }
/** /**
* Sanitizes a Rut string. * Retorna el RUT en formato "12345678-K".
*
* @param string $value
*
* @return string
*/ */
private function sanitize(string $value): string public function toSimple(): string
{ {
return str_replace(['.', ',', '-'], '', strtoupper(trim($value))); return $this->number.'-'.$this->verifier->toString();
} }
/** /**
* Helper to format the Rut as FORMAT_READABLE. * Retorna el RUT en formato "12.345.678-K".
*
* @return string
*/ */
private function formatReadable(): string public function toHuman(): string
{ {
return sprintf('%s-%s', number_format($this->value, 0, '', '.'), $this->dv); return \number_format($this->number, 0, ',', '.').'-'.$this->verifier->toString();
} }
/** /**
* Helper to format the Rut as FORMAT_HIDDEN. * Retorna los $n ultimos numeros del RUT.
* *
* @return string * El digito verificador no es considerado.
*/ */
private function formatHidden(): string public function last(int $n, string $pad = ''): string
{ {
$readable = $this->formatReadable(); $number = (string) $this->number;
$exploded = explode('.', $readable); $last = \substr($number, -$n);
if ($pad !== '') {
$last = \str_repeat($pad, \strlen($number) - $n).$last;
}
return sprintf('%s.***.***-%s', $exploded[0], $this->dv); return $last;
}
/**
* Retorna los $n primeros numeros del RUT.
*
* El digito verificador no es considerado.
*/
public function first(int $n, string $pad = ''): string
{
$number = (string) $this->number;
$first = \substr($number, 0, $n);
if ($pad !== '') {
$first .= \str_repeat($pad, \strlen($number) - $n);
}
return $first;
}
/**
* Computa el digito verificador de un RUT a partir del numero.
*/
public static function computeVerifier(int $number): Verifier
{
$sequence = \array_reverse(\array_map(
static fn (string $d): int => (int) $d,
\str_split((string) $number)
));
$x = 2;
$s = 0;
foreach ($sequence as $digit) {
if ($x > 7) {
$x = 2;
}
$s += $digit * $x;
++$x;
}
return Verifier::from(11 - ($s % 11));
} }
} }

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
/**
* @project Chilean RUT
* @link https://github.com/mnavarrocarter/chilean-rut
* @package castor/log
* @author Matias Navarro-Carter mnavarrocarter@gmail.com
* @license MIT
* @copyright 2024 Matias Navarro-Carter
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MNC\Rut\Doctrine;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\IntegerType;
use MNC\Rut;
/**
* Mapea el Rut a una columna integer.
*/
class NumericRutType extends IntegerType
{
public const NAME = 'numeric-rut';
public function getName(): string
{
return self::NAME;
}
/**
* @param mixed $value
*
* @throws ConversionException
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
{
if ($value === null) {
return null;
}
if ($value instanceof Rut) {
return (string) $value->number;
}
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', Rut::class]);
}
/**
* @param mixed $value
*
* @throws ConversionException
*/
public function convertToPHPValue($value, AbstractPlatform $platform): ?Rut
{
$value = parent::convertToPHPValue($value, $platform);
if ($value === null) {
return null;
}
return Rut::create($value);
}
public function requiresSQLCommentHint(AbstractPlatform $platform): true
{
return true;
}
}

View File

@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
/**
* @project Chilean RUT
* @link https://github.com/mnavarrocarter/chilean-rut
* @package castor/log
* @author Matias Navarro-Carter mnavarrocarter@gmail.com
* @license MIT
* @copyright 2024 Matias Navarro-Carter
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MNC\Rut\Doctrine;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\StringType;
use MNC\Rut;
/**
* Mapea el RUT a una columna VARCHAR.
*/
class RutType extends StringType
{
public const NAME = 'rut';
public function getName(): string
{
return self::NAME;
}
/**
* @param mixed $value
*
* @throws ConversionException
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
{
if ($value === null) {
return null;
}
if ($value instanceof Rut) {
return $value->toHuman();
}
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', Rut::class]);
}
/**
* @param mixed $value
*
* @throws ConversionException
*/
public function convertToPHPValue($value, AbstractPlatform $platform): ?Rut
{
if ($value === null) {
return null;
}
if (\is_string($value)) {
return Rut::parse($value);
}
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'string']);
}
public function requiresSQLCommentHint(AbstractPlatform $platform): true
{
return true;
}
}

53
src/Rut/IsInvalid.php Normal file
View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
/**
* @project Chilean RUT
* @link https://github.com/mnavarrocarter/chilean-rut
* @package castor/log
* @author Matias Navarro-Carter mnavarrocarter@gmail.com
* @license MIT
* @copyright 2024 Matias Navarro-Carter
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MNC\Rut;
class IsInvalid extends \InvalidArgumentException
{
public static function numberTooBig(int $number): IsInvalid
{
return new self(\sprintf(
'El RUT numero %d es mayor a 99.999.999',
$number
));
}
public static function numberTooSmall(int $number): IsInvalid
{
return new self(\sprintf(
'El RUT numero %d es menor a cero',
$number
));
}
public static function rut(int $number, Verifier $verifier): IsInvalid
{
return new self(\sprintf(
'El digito verificador %s no es valido para el rut %d',
$verifier->toString(),
$number
));
}
public static function verifier(string $verifier): IsInvalid
{
return new self(\sprintf(
'Encontrado un digito verificador invalido con valor %s',
$verifier,
));
}
}

78
src/Rut/Verifier.php Normal file
View File

@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
/**
* @project Chilean RUT
* @link https://github.com/mnavarrocarter/chilean-rut
* @package castor/log
* @author Matias Navarro-Carter mnavarrocarter@gmail.com
* @license MIT
* @copyright 2024 Matias Navarro-Carter
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MNC\Rut;
/**
* Representa el digito verificador de un RUT.
*
* Internamente, se guarda el modulo del RUT, y no su valor como texto.
*/
enum Verifier: int
{
case One = 1;
case Two = 2;
case Three = 3;
case Four = 4;
case Five = 5;
case Six = 6;
case Seven = 7;
case Eight = 8;
case Nine = 9;
case K = 10;
case Zero = 11;
/**
* @throws IsInvalid si el verificador es invalido
*/
public static function fromString(string $v): Verifier
{
return match ($v) {
'1' => self::One,
'2' => self::Two,
'3' => self::Three,
'4' => self::Four,
'5' => self::Five,
'6' => self::Six,
'7' => self::Seven,
'8' => self::Eight,
'9' => self::Nine,
'0' => self::Zero,
'K' => self::K,
default => throw IsInvalid::verifier($v)
};
}
/**
* Retorna la representacion textual del digito verificador.
*/
public function toString(): string
{
return match ($this) {
self::One => '1',
self::Two => '2',
self::Three => '3',
self::Four => '4',
self::Five => '5',
self::Six => '6',
self::Seven => '7',
self::Eight => '8',
self::Nine => '9',
self::K => 'K',
self::Zero => '0',
};
}
}

View File

@@ -1,78 +0,0 @@
<?php
/*
* This file is part of the MNC\ChileanRut library.
*
* (c) Matías Navarro Carter <mnavarrocarter@gmail.com>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MNC\ChileanRut\Util;
use MNC\ChileanRut\Rut;
/**
* This class provides utils for a Rut correlative.
*
* @author Matías Navarro Carter <mnavarro@option.cl>
*/
class CorrelativeUtils
{
/**
* 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 (10 === $dv) {
$dv = 'K';
}
if (11 === $dv) {
$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));
}
/**
* Auto-generates an algorithmically valid Rut, because why not.
*
* @return Rut
*/
public static function autoGenerateValidRut(): Rut
{
$correlative = \random_int(1000000, 40000000);
return static::createValidRutOnlyFromCorrelative($correlative);
}
}

View File

@@ -1,52 +0,0 @@
<?php
/*
* This file is part of the MNC\ChileanRut library.
*
* (c) Matías Navarro Carter <mnavarrocarter@gmail.com>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MNC\ChileanRut\Validator;
use MNC\ChileanRut\Exception\InvalidRutException;
use MNC\ChileanRut\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.
*
* @param RutValidator[] $validators
*/
public function __construct(RutValidator ...$validators)
{
$this->validators = $validators;
}
/**
* @param Rut $rut
*
* @throws InvalidRutException on invalid Rut
*/
public function validate(Rut $rut): void
{
foreach ($this->validators as $validator) {
$validator->validate($rut);
}
}
}

View File

@@ -1,37 +0,0 @@
<?php
/*
* This file is part of the MNC\ChileanRut library.
*
* (c) Matías Navarro Carter <mnavarrocarter@gmail.com>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MNC\ChileanRut\Validator;
use MNC\ChileanRut\Exception\InvalidRutException;
use MNC\ChileanRut\Rut;
use MNC\ChileanRut\Util\CorrelativeUtils;
/**
* Validates the Rut using the Module 11 algorithm.
*
* @author Matías Navarro Carter <mnavarro@option.cl>
*/
class Module11RutValidator implements RutValidator
{
/**
* @param Rut $rut
*
* @throws InvalidRutException
*/
public function validate(Rut $rut): void
{
$digit = CorrelativeUtils::findVerifierDigit($rut->getCorrelative());
if ($digit !== $rut->getVerifierDigit()) {
throw new InvalidRutException($rut);
}
}
}

View File

@@ -1,43 +0,0 @@
<?php
/*
* This file is part of the MNC\ChileanRut library.
*
* (c) Matías Navarro Carter <mnavarrocarter@gmail.com>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MNC\ChileanRut\Validator;
use MNC\ChileanRut\Exception\InvalidRutException;
use MNC\ChileanRut\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 Module11RutValidator 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.
*
* @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

@@ -1,41 +0,0 @@
<?php
/*
* This file is part of the MNC\ChileanRut library.
*
* (c) Matías Navarro Carter <mnavarrocarter@gmail.com>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MNC\ChileanRut\Tests\Bridge\Symfony\Form;
use MNC\ChileanRut\Bridge\Symfony\Form\RutType;
use MNC\ChileanRut\Rut;
use Symfony\Component\Form\Test\TypeTestCase;
class RutTypeTest extends TypeTestCase
{
public function testSubmitValidData()
{
$objectToCompare = new Rut('16.894.365-2');
// $objectToCompare will retrieve data from the form submission; pass it as the second argument
$form = $this->factory->create(RutType::class);
// submit the data to the form directly
$form->submit('16.894.365-2');
$this->assertTrue($form->isSynchronized());
$formData = $form->getData();
// check that $objectToCompare was modified as expected when the form was submitted
$this->assertInstanceOf(Rut::class, $formData);
$this->assertTrue($formData->isEqualTo($objectToCompare));
$view = $form->createView();
$this->assertSame('16.894.365-2', $view->vars['value']);
}
}

View File

@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
/**
* @project Chilean RUT
* @link https://github.com/mnavarrocarter/chilean-rut
* @package castor/log
* @author Matias Navarro-Carter mnavarrocarter@gmail.com
* @license MIT
* @copyright 2024 Matias Navarro-Carter
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MNC\Rut\Doctrine;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\Type;
use MNC\Rut;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
#[CoversClass(NumericRutType::class)]
#[CoversClass(Rut::class)]
#[CoversClass(Rut\Verifier::class)]
class NumericRutTypeTest extends TestCase
{
public static function setUpBeforeClass(): void
{
Type::addType(NumericRutType::NAME, NumericRutType::class);
}
public function testItConvertsNullFromDatabaseValue(): void
{
$platform = $this->createMock(AbstractPlatform::class);
$type = Type::getType(NumericRutType::NAME);
$result = $type->convertToPHPValue(null, $platform);
self::assertNull($result);
}
public function testItConvertsStringFromDatabaseValue(): void
{
$platform = $this->createMock(AbstractPlatform::class);
$type = Type::getType(NumericRutType::NAME);
$result = $type->convertToPHPValue(16894365, $platform);
self::assertInstanceOf(Rut::class, $result);
}
public function testItConvertsNullToDatabaseValue(): void
{
$platform = $this->createMock(AbstractPlatform::class);
$type = Type::getType(NumericRutType::NAME);
$result = $type->convertToDatabaseValue(null, $platform);
self::assertNull($result);
}
public function testItConvertsRutToDatabaseValue(): void
{
$platform = $this->createMock(AbstractPlatform::class);
$type = Type::getType(NumericRutType::NAME);
$result = $type->convertToDatabaseValue(Rut::parse('168943652'), $platform);
self::assertSame('16894365', $result);
}
public function testItCannotConvertToDatabaseValue(): void
{
$platform = $this->createMock(AbstractPlatform::class);
$type = Type::getType(NumericRutType::NAME);
$this->expectException(ConversionException::class);
$type->convertToDatabaseValue(new \DateTime(), $platform);
}
#[Test]
public function it_returns_true_for_comment(): void
{
$platform = $this->createMock(AbstractPlatform::class);
$this->assertTrue(Type::getType(NumericRutType::NAME)->requiresSQLCommentHint($platform));
}
}

View File

@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
/**
* @project Chilean RUT
* @link https://github.com/mnavarrocarter/chilean-rut
* @package castor/log
* @author Matias Navarro-Carter mnavarrocarter@gmail.com
* @license MIT
* @copyright 2024 Matias Navarro-Carter
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MNC\Rut\Doctrine;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\Type;
use MNC\Rut;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
#[CoversClass(RutType::class)]
#[CoversClass(Rut::class)]
#[CoversClass(Rut\Verifier::class)]
class RutTypeTest extends TestCase
{
public static function setUpBeforeClass(): void
{
Type::addType(RutType::NAME, RutType::class);
}
public function testItConvertsNullFromDatabaseValue(): void
{
$platform = $this->createMock(AbstractPlatform::class);
$type = Type::getType(RutType::NAME);
$result = $type->convertToPHPValue(null, $platform);
self::assertNull($result);
}
public function testItConvertsStringFromDatabaseValue(): void
{
$platform = $this->createMock(AbstractPlatform::class);
$type = Type::getType(RutType::NAME);
$result = $type->convertToPHPValue('16.894.365-2', $platform);
self::assertInstanceOf(Rut::class, $result);
}
public function testItCannotConvertFromDatabaseValue(): void
{
$platform = $this->createMock(AbstractPlatform::class);
$type = Type::getType(RutType::NAME);
$this->expectException(ConversionException::class);
$type->convertToPHPValue(true, $platform);
}
public function testItConvertsNullToDatabaseValue(): void
{
$platform = $this->createMock(AbstractPlatform::class);
$type = Type::getType(RutType::NAME);
$result = $type->convertToDatabaseValue(null, $platform);
self::assertNull($result);
}
public function testItConvertsRutToDatabaseValue(): void
{
$platform = $this->createMock(AbstractPlatform::class);
$type = Type::getType(RutType::NAME);
$result = $type->convertToDatabaseValue(Rut::parse('168943652'), $platform);
self::assertSame('16.894.365-2', $result);
}
public function testItCannotConvertToDatabaseValue(): void
{
$platform = $this->createMock(AbstractPlatform::class);
$type = Type::getType(RutType::NAME);
$this->expectException(ConversionException::class);
$type->convertToDatabaseValue(new \DateTime(), $platform);
}
#[Test]
public function it_returns_true_for_comment(): void
{
$platform = $this->createMock(AbstractPlatform::class);
$this->assertTrue(Type::getType(RutType::NAME)->requiresSQLCommentHint($platform));
}
}

View File

@@ -1,82 +0,0 @@
<?php
/*
* This file is part of the MNC\ChileanRut library.
*
* (c) Matías Navarro Carter <mnavarrocarter@gmail.com>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MNC\ChileanRut\Tests\Rut;
use MNC\ChileanRut\Exception\InvalidRutException;
use MNC\ChileanRut\Rut;
use MNC\ChileanRut\Validator\Module11RutValidator;
use PHPUnit\Framework\TestCase;
class RutTest extends TestCase
{
public function testThatRutIsSanitizedProperlyOnInstantiation()
{
$rut = Rut::fromString('16.894.365-2');
$this->assertSame('16894365', $rut->getCorrelative());
$this->assertSame('2', $rut->getVerifierDigit());
}
public function testThatRutsInstantiatedDifferentFormatButWithEqualValueAreIndeedEqual()
{
$rut1 = new Rut('16.894.365-2');
$rut2 = new Rut('16894365-2');
$this->assertTrue($rut1->isEqualTo($rut2));
}
public function testThatFormatClearWorks()
{
$rut = new Rut('16.894.365-2');
$this->assertSame('168943652', $rut->format(Rut::FORMAT_CLEAR));
}
public function testThatFormatWithHyphenWorks()
{
$rut = new Rut('16.894.365-2');
$this->assertSame('16894365-2', $rut->format(Rut::FORMAT_HYPHENED));
}
public function testThatFormatReadableWorks()
{
$rut = new Rut('168943652');
$this->assertSame('16.894.365-2', $rut->format(Rut::FORMAT_READABLE));
}
public function testThatFormatHiddenWorks()
{
$rut = new Rut('168943652');
$this->assertSame('16.***.***-2', $rut->format(Rut::FORMAT_HIDDEN));
}
public function testThatIntegratedValidationThrowsExceptionOnInvalidRut()
{
$this->expectException(InvalidRutException::class);
$validator = new Module11RutValidator();
$rut = new Rut('4444444-2', $validator);
}
public function testThatIntegratedValidationDoesNotThrowExceptionOnValidRut()
{
$validator = new Module11RutValidator();
$rut = new Rut('16.894.365-2', $validator);
$this->assertInstanceOf(Rut::class, $rut);
}
public function testInvalidFormatValueRaisesException()
{
$rut = new Rut('16.894.365-2');
$this->expectException(\InvalidArgumentException::class);
$rut->format(23);
}
}

107
tests/RutTest.php Normal file
View File

@@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
/**
* @project Chilean RUT
* @link https://github.com/mnavarrocarter/chilean-rut
* @package castor/log
* @author Matias Navarro-Carter mnavarrocarter@gmail.com
* @license MIT
* @copyright 2024 Matias Navarro-Carter
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MNC;
use MNC\Rut\IsInvalid;
use MNC\Rut\Verifier;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
#[CoversClass(Rut::class)]
#[CoversClass(IsInvalid::class)]
#[CoversClass(Verifier::class)]
class RutTest extends TestCase
{
#[Test]
#[DataProvider('getParseData')]
public function it_parses_ruts(string $raw, int $expectedNumber, Verifier $expectedVerifier): void
{
$rut = Rut::parse($raw);
$this->assertSame($expectedNumber, $rut->number);
$this->assertSame($expectedVerifier, $rut->verifier);
}
#[Test]
#[DataProvider('getParseWithErrorData')]
public function it_parses_with_error(string $raw, string $expectedError): void
{
$this->expectException(IsInvalid::class);
$this->expectExceptionMessage($expectedError);
Rut::parse($raw);
}
#[Test]
public function it_cannot_be_negative(): void
{
$this->expectException(IsInvalid::class);
$this->expectExceptionMessage('El RUT numero -22224525 es menor a cero');
Rut::create(-22_224_525);
}
#[Test]
public function it_checks_for_equality(): void
{
$rut1 = Rut::parse('168943652');
$rut2 = Rut::parse('16.894.365-2');
$rut3 = Rut::create(22_224_525);
$this->assertTrue($rut1->equals($rut2));
$this->assertFalse($rut1->equals($rut3));
}
#[Test]
public function it_creates_with_no_verifier(): void
{
$rut = Rut::create(22_457_309);
$this->assertSame(22_457_309, $rut->number);
}
#[Test]
public function it_formats(): void
{
$rut = Rut::create(22_457_309);
$this->assertSame('22457309K', $rut->toString());
$this->assertSame('22457309-K', $rut->toSimple());
$this->assertSame('22.457.309-K', $rut->toHuman());
$this->assertSame('7309', $rut->last(4));
$this->assertSame('****7309', $rut->last(4, '*'));
$this->assertSame('2245', $rut->first(4));
$this->assertSame('2245****', $rut->first(4, '*'));
}
public static function getParseData(): array
{
return [
['16.894.365-2', 16_894_365, Verifier::Two],
['24 736.7322', 24_736_732, Verifier::Two],
[' 24 232.. 442 -- 0', 24_232_442, Verifier::Zero],
['35323325', 3_532_332, Verifier::Five],
['22.457.309K', 22_457_309, Verifier::K],
['15450088K', 15_450_088, Verifier::K],
];
}
public static function getParseWithErrorData(): array
{
return [
['212321312321-1', 'El RUT numero 212321312321 es mayor a 99.999.999'],
['23.232.123-K', 'El digito verificador K no es valido para el rut 23232123'],
['12.2324.232-P', 'Encontrado un digito verificador invalido con valor P'],
];
}
}

View File

@@ -1,56 +0,0 @@
<?php
/*
* This file is part of the MNC\ChileanRut library.
*
* (c) Matías Navarro Carter <mnavarrocarter@gmail.com>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MNC\ChileanRut\Tests\Validator;
use MNC\ChileanRut\Exception\InvalidRutException;
use MNC\ChileanRut\Util\CorrelativeUtils;
use MNC\ChileanRut\Validator\ChainRutValidator;
use MNC\ChileanRut\Validator\Module11RutValidator;
use MNC\ChileanRut\Validator\RutValidator;
use PHPUnit\Framework\TestCase;
/**
* Class ChainRutValidatorTest.
*/
class ChainRutValidatorTest extends TestCase
{
public function testThatChainValidatorFails(): void
{
$rut = CorrelativeUtils::autoGenerateValidRut();
$normalValidator = new Module11RutValidator();
$mockValidator = $this->createMock(RutValidator::class);
$mockValidator->expects($this->once())
->method('validate')
->willThrowException(new InvalidRutException($rut));
$chainValidator = new ChainRutValidator($normalValidator, $mockValidator);
$this->expectException(InvalidRutException::class);
$chainValidator->validate($rut);
}
public function testThatChainValidatorPasses(): void
{
$rut = CorrelativeUtils::autoGenerateValidRut();
$normalValidator = new Module11RutValidator();
$mockValidator = $this->createMock(RutValidator::class);
$mockValidator->expects($this->once())
->method('validate')
->willReturn(null);
$chainValidator = new ChainRutValidator($normalValidator, $mockValidator);
$chainValidator->validate($rut);
$this->assertTrue(true);
}
}

View File

@@ -1,39 +0,0 @@
<?php
/*
* This file is part of the MNC\ChileanRut library.
*
* (c) Matías Navarro Carter <mnavarrocarter@gmail.com>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MNC\ChileanRut\Tests\Validator;
use MNC\ChileanRut\Exception\InvalidRutException;
use MNC\ChileanRut\Rut;
use MNC\ChileanRut\Validator\Module11RutValidator;
use PHPUnit\Framework\TestCase;
class SimpleRutValidatorTest extends TestCase
{
public function testValidationPassesOnValidRut()
{
$rut = new Rut('16.894.365-2');
$validator = new Module11RutValidator();
$validator->validate($rut);
$this->assertInstanceOf(Rut::class, $rut);
}
public function testValidationFailsOnInvalidRut()
{
$this->expectException(InvalidRutException::class);
$rut = new Rut('34.4534.353-1');
$validator = new Module11RutValidator();
$validator->validate($rut);
}
}