From 9d1841288b74737fbe747a944927a8309f3744f3 Mon Sep 17 00:00:00 2001 From: Vitaliy IIIFX Khomenko Date: Sat, 3 Dec 2016 23:54:00 +0200 Subject: [PATCH 1/2] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=BF=D0=B0=D1=80=D1=81=D0=B5=D1=80,=20=D0=BD=D0=B0=D0=BF?= =?UTF-8?q?=D0=B8=D1=81=D0=B0=D0=BB=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0?= =?UTF-8?q?=D1=82=D0=BE=D1=80,=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/Builder.php | 180 +++++++++++++++++++++++++++++++++++- source/Parser.php | 206 +++++++++++++++++++----------------------- tests/BuilderTest.php | 108 ++++++++++++++++++++++ tests/ParserTest.php | 118 ++++++++++++++++++------ 4 files changed, 471 insertions(+), 141 deletions(-) create mode 100644 tests/BuilderTest.php diff --git a/source/Builder.php b/source/Builder.php index 4156967..766c38b 100644 --- a/source/Builder.php +++ b/source/Builder.php @@ -2,10 +2,186 @@ namespace iiifx\Identification\Ukraine; +use DateTime; +use DateTimeImmutable; +use DateTimeInterface; +use InvalidArgumentException; + class Builder { - public function __construct () + /** + * Male + */ + const SEX_MALE = 1; + + /** + * Female + */ + const SEX_FEMALE = 2; + + /** + * @var int + */ + protected $personSex; + + /** + * @var DateTimeImmutable + */ + protected $birthDatetime; + + /** + * @param int|null $sex + * @param DateTimeInterface|null $datetime + * + * @return static + */ + public static function create ( $sex = null, DateTimeInterface $datetime = null ) + { + $builder = new static(); + if ( $sex ) { + $builder->setPersonSex( $sex ); + } + if ( $datetime ) { + $builder->setPersonBirthDatetime( $datetime ); + } + return $builder; + } + + /** + * @param $sex + * + * @return $this + * + * @throws InvalidArgumentException + */ + public function setPersonSex ( $sex ) + { + $allow = [ + static::SEX_MALE, + static::SEX_FEMALE, + ]; + if ( in_array( $sex, $allow, true ) ) { + $this->personSex = $sex; + return $this; + } + throw new InvalidArgumentException( '$sex must be Builder::SEX_MALE or Builder::SEX_FEMALE' ); + } + + /** + * @return $this + */ + public function setPersonMale () + { + $this->personSex = static::SEX_MALE; + return $this; + } + + /** + * @return $this + */ + public function setPersonFemale () + { + $this->personSex = static::SEX_FEMALE; + return $this; + } + + /** + * @param int $age + * + * @return $this + * + * @throws InvalidArgumentException + */ + public function setPersonAge ( $age ) + { + if ( is_int( $age ) && $age >= 0 ) { + $datetime = ( new DateTimeImmutable( 'now' ) )->modify( "-{$age} years -1 day" ); + return $this->setPersonBirthDatetime( $datetime ); + } + throw new InvalidArgumentException( '$age must be positive integer' ); + } + + /** + * @param DateTimeInterface $datetime + * + * @return $this + * + * @throws InvalidArgumentException + */ + public function setPersonBirthDatetime ( DateTimeInterface $datetime ) + { + if ( $datetime->format( 'Y' ) >= 1900 ) { + if ( !$datetime instanceof DateTimeImmutable ) { + $datetime = new DateTimeImmutable( $datetime->format( 'Y-m-d H:i:s' ) ); + } + $this->birthDatetime = $datetime; + return $this; + } + throw new InvalidArgumentException( 'Birthday must be equal or greater than 1900-01-01' ); + } + + /** + * Format: DDDDDXXXSC + * + * DDDDD - Days from 1900-01-01 + * XXX - Any nambers + * S - Person sex + * C - Control digit + * + * @return string + */ + public function createNumber () + { + if ( $this->birthDatetime ) { + $days = (int) $this->birthDatetime + ->diff( new DateTime( '1900-01-01 00:00:00' ) ) + ->days; + } else { + $max = (int) ( new DateTime( '1900-01-01 00:00:00' ) ) + ->diff( new DateTime() ) + ->days; + $days = mt_rand( 0, $max ); + } + $DDDDD = sprintf( '%1$05d', $days + 2 ); + $XXX = sprintf( '%1$03d', mt_rand( 0, 999 ) ); + if ( !( $sex = $this->personSex ) ) { + $list = [ static::SEX_MALE => 0, static::SEX_FEMALE => 0 ]; + $sex = array_rand( $list ); + } + if ( $sex === static::SEX_FEMALE ) { + $list = [ 0 => 0, 2 => 0, 4 => 0, 6 => 0, 8 => 0 ]; + } else { + $list = [ 1 => 0, 3 => 0, 5 => 0, 7 => 0, 9 => 0 ]; + } + $S = array_rand( $list ); + $number = "{$DDDDD}{$XXX}{$S}"; + $summ = + ( $number{0} * -1 ) + + ( $number{1} * 5 ) + + ( $number{2} * 7 ) + + ( $number{3} * 9 ) + + ( $number{4} * 4 ) + + ( $number{5} * 6 ) + + ( $number{6} * 10 ) + + ( $number{7} * 5 ) + + ( $number{8} * 7 ); + $C = ( $summ % 11 ) % 10; + return "{$number}{$C}"; + } + + /** + * @return int|null + */ + public function getPersonSex () + { + return $this->personSex; + } + + /** + * @return DateTimeImmutable|null + */ + public function getPersonBirthDatetime () { - throw new \LogicException( 'Not implemented' ); + return $this->birthDatetime; } } diff --git a/source/Parser.php b/source/Parser.php index f6a8f4d..f7ddac4 100644 --- a/source/Parser.php +++ b/source/Parser.php @@ -10,51 +10,47 @@ class Parser { /** - * Номер ИНН - * + * Male + */ + const SEX_MALE = 1; + + /** + * Female + */ + const SEX_FEMALE = 2; + + /** * @var string */ protected $number; /** - * Части номера, по цифрам - * - * @var string[] + * @var bool */ - protected $parts; + protected $isValid; /** - * Корректный ли номер ИНН - * - * @var bool + * @var int */ - protected $valid; + protected $controlSumm; /** - * Контрольное число - * - * @var int|false + * @var int */ - protected $control; + protected $controlDigit; /** - * Пол владельца - * - * @var string|false + * @var int */ - protected $sex; + protected $personSex; /** - * Дата рождения владельца - * - * @var DateTimeImmutable|false + * @var DateTimeImmutable */ - protected $datetime; + protected $birthDatetime; /** - * Фабрика - * - * @param string|int $number + * @param string $number * * @return Parser */ @@ -64,17 +60,46 @@ public static function create ( $number ) } /** - * Базовый конструктор - * * @param string $number */ public function __construct ( $number ) { $this->number = (string) $number; + $this->parseNumber(); + } + + /** + * Parser + */ + protected function parseNumber () + { + if ( preg_match( '/^\d{10}$/', $this->number ) === 1 ) { + $this->controlSumm = + ( $this->number{0} * -1 ) + + ( $this->number{1} * 5 ) + + ( $this->number{2} * 7 ) + + ( $this->number{3} * 9 ) + + ( $this->number{4} * 4 ) + + ( $this->number{5} * 6 ) + + ( $this->number{6} * 10 ) + + ( $this->number{7} * 5 ) + + ( $this->number{8} * 7 ); + $this->controlDigit = ( $this->controlSumm % 11 ) % 10; + $this->isValid = $this->controlDigit === (int) $this->number{9}; + if ( $this->isValid ) { + $this->personSex = ( $this->number{8} % 2 ) ? static::SEX_MALE : static::SEX_FEMALE; + $datetime = new DateTimeImmutable( + '1900-01-01 00:00:00', + new DateTimeZone( 'UTC' ) + ); + $days = substr( $this->number, 0, 5 ) - 1; + $this->birthDatetime = $datetime->modify( "+ {$days} days" ); + } + } } /** - * @return int|string + * @return string|null */ public function getNumber () { @@ -82,132 +107,87 @@ public function getNumber () } /** - * Определить корректный ли номер ИНН - * - * @return bool + * @return int|null */ - public function isValid () + public function getControlDigit () { - if ( $this->valid === null ) { - if ( $this->parseNumber() ) { - $this->valid = $this->getControlDigit() === (int) $this->parts[ 9 ]; - } - } - return $this->valid; + return $this->controlDigit; } /** - * Получить пол владельца номера ИНН - * - * @return string + * @return int|null */ - public function getSex () + public function getControlSumm () { - if ( $this->sex === null ) { - $this->sex = false; - if ( $this->isValid() ) { - $this->sex = ( $this->parts[ 8 ] % 2 ) ? 'M' : 'F'; - } - } - return $this->sex; + return $this->controlSumm; } /** - * Мужчина ли владелец номера ИНН - * - * @return bool + * @return bool|null */ - public function isMale () + public function isValidNumber () { - return $this->getSex() === 'M'; + return $this->isValid; } /** - * Женщина ли владелец номера ИНН - * - * @return bool + * @return int|null */ - public function isFemale () + public function getPersonSex () { - return $this->getSex() === 'F'; + return $this->personSex; } /** - * Получить возраст владельца номера ИНН - * - * @param DateTimeInterface|null $now - * - * @return int + * @return bool|null */ - public function getAge ( DateTimeInterface $now = null ) + public function isPersonMale () { - if ( $now === null ) { - $now = new DateTime( 'now', new DateTimeZone( 'UTC' ) ); - $now->modify( 'midnight' ); - } - return (int) $now->diff( $this->getBirthDatetime() )->y; + return $this->getPersonSex() === static::SEX_MALE; } /** - * Получить дату рождения владельца ИНН - * - * @return DateTimeImmutable|false + * @return bool|null */ - public function getBirthDatetime () + public function isPersonFemale () { - if ( $this->datetime === null ) { - $this->datetime = false; - if ( $this->isValid() ) { - $days = substr( $this->number, 0, 5 ) - 1; - $datetime = new DateTimeImmutable( - '1900-01-01 00:00:00', - new DateTimeZone( 'UTC' ) - ); - $this->datetime = $datetime->modify( "+ {$days} days" ); - } - } - return $this->datetime; + return $this->getPersonSex() === static::SEX_FEMALE; + } + + /** + * @return DateTimeImmutable|null + */ + public function getPersonBirthDatetime () + { + return $this->birthDatetime; } /** - * Распарсить номер ИНН + * @param string $format * - * @return bool + * @return string|null */ - protected function parseNumber () + public function getPersonBirth ( $format = 'Y-m-d' ) { - if ( $this->parts === null ) { - $this->parts = []; - if ( preg_match( '/^\d{10}$/', $this->number ) === 1 ) { - $this->parts = str_split( $this->number ); - } + if ( $datetime = $this->getPersonBirthDatetime() ) { + return $datetime->format( $format ); } - return (bool) $this->parts; + return null; } /** - * Получить контрольное число номера ИНН + * @param DateTimeInterface|null $now * - * @return int + * @return int|null */ - protected function getControlDigit () + public function getPersonAge ( DateTimeInterface $now = null ) { - if ( $this->control === null ) { - $this->control = false; - if ( $this->parseNumber() ) { - $summ = - $this->parts[ 0 ] * -1 - + $this->parts[ 1 ] * 5 - + $this->parts[ 2 ] * 7 - + $this->parts[ 3 ] * 9 - + $this->parts[ 4 ] * 4 - + $this->parts[ 5 ] * 6 - + $this->parts[ 6 ] * 10 - + $this->parts[ 7 ] * 5 - + $this->parts[ 8 ] * 7; - $this->control = $summ % 11; + if ( $datetime = $this->getPersonBirthDatetime() ) { + if ( $now === null ) { + $now = new DateTime( 'now', new DateTimeZone( 'UTC' ) ); } + return (int) $now->diff( $datetime )->y; } - return $this->control; + return null; } } diff --git a/tests/BuilderTest.php b/tests/BuilderTest.php new file mode 100644 index 0000000..7902590 --- /dev/null +++ b/tests/BuilderTest.php @@ -0,0 +1,108 @@ +diff( new DateTime() )->y, true ], + [ Builder::SEX_MALE, '1900-01-01', ( new DateTime( '1900-01-01' ) )->diff( new DateTime() )->y, true ], + [ Builder::SEX_FEMALE, '2000-12-31', ( new DateTime( '2000-12-31' ) )->diff( new DateTime() )->y, true ], + [ true, '2000-12-31', -1, false ], + [ 'hello', '2000-12-31', false, false ], + [ 1, '1801-02-03', 'hello', false ], + ]; + } + + /** + * @dataProvider getData + */ + public function testFactory ( $sex, $date, $age, $valid ) + { + if ( $valid ) { + $this->assertInstanceOf( Builder::class, Builder::create( $sex, new DateTime( $date ) ) ); + } + } + + /** + * @dataProvider getData + */ + public function testSetPersonSex ( $sex, $date, $age, $valid ) + { + if ( $valid ) { + $this->assertInstanceOf( Builder::class, Builder::create( $sex, new DateTime( $date ) ) ); + } else { + $this->setExpectedException( InvalidArgumentException::class ); + Builder::create( $sex, new DateTime( $date ) ); + } + } + + /** + * @dataProvider getData + */ + public function testSetPersonMaleFemale ( $sex, $date, $age, $valid ) + { + if ( $valid ) { + $builder = new Builder(); + if ( $sex === Builder::SEX_MALE ) { + $builder->setPersonMale(); + } else { + $builder->setPersonFemale(); + } + $this->assertEquals( $sex, Builder::create( $sex )->getPersonSex() ); + } else { + $this->setExpectedException( InvalidArgumentException::class ); + Builder::create( $sex, new DateTime( $date ) ); + } + } + + /** + * @dataProvider getData + */ + public function testSetPersonAge ( $sex, $date, $age, $valid ) + { + $builder = new Builder(); + if ( $valid ) { + $builder->setPersonAge( $age ); + $this->assertEquals( $age, $builder->getPersonBirthDatetime()->diff( new DateTime() )->y ); + } else { + $this->setExpectedException( InvalidArgumentException::class ); + $builder->setPersonAge( $age ); + } + } + + /** + * @dataProvider getData + */ + public function testSetPersonBirthDatetime ( $sex, $date, $age, $valid ) + { + if ( $valid ) { + $builder = new Builder(); + $builder->setPersonBirthDatetime( new DateTime( $date ) ); + $this->assertEquals( $age, $builder->getPersonBirthDatetime()->diff( new DateTime() )->y ); + } + } + + /** + * @dataProvider getData + */ + public function testCreateNumber ( $sex, $date, $age, $valid ) + { + $builder = Builder::create(); + if ( $valid ) { + $builder->setPersonSex( $sex ); + $builder->setPersonAge( $age ); + $this->assertEquals( 10, strlen( $builder->createNumber() ) ); + } else { + $this->assertEquals( 10, strlen( $builder->createNumber() ) ); + } + } +} diff --git a/tests/ParserTest.php b/tests/ParserTest.php index b9d914b..5fb54a8 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -9,69 +9,135 @@ class ParserTest extends PHPUnit_Framework_TestCase * * @return array */ - public function getDataProviders () + public function getData () { return [ - [ '3080310794', 'M', '1984-05-02', true ], + # $number, $valid, $sex, $date, $summ, $digit + [ '0123456789', false, null, null, null, null ], + [ 1234567890, false, null, null, null, null ], + + [ '3209019722', true, 2, '1987-11-10', 233, 2 ], + [ '2534202383', true, 2, '1969-05-20', 179, 3 ], + [ '3928187406', true, 2, '2007-07-19', 270, 6 ], + [ '4052326370', true, 1, '2010-12-12', 197, 0 ], + [ '0663355735', true, 1, '1918-02-28', 247, 5 ], + [ '2917826131', true, 1, '1979-11-20', 243, 1 ], ]; } /** - * @dataProvider getDataProviders + * @dataProvider getData */ - public function testFactory ( $number, $sex, $date, $valid ) + public function testFactory ( $number, $valid, $sex, $date, $summ, $digit ) { $this->assertInstanceOf( Parser::class, Parser::create( $number ) ); } /** - * @dataProvider getDataProviders + * @dataProvider getData */ - public function testGetNumber ( $number, $sex, $date, $valid ) + public function testGetNumber ( $number, $valid, $sex, $date, $summ, $digit ) { $this->assertEquals( $number, Parser::create( $number )->getNumber() ); } /** - * @dataProvider getDataProviders + * @dataProvider getData + */ + public function testGetControlSumm ( $number, $valid, $sex, $date, $summ, $digit ) + { + if ( $valid ) { + $this->assertEquals( $summ, Parser::create( $number )->getControlSumm() ); + } + } + + /** + * @dataProvider getData + */ + public function testGetControlDigit ( $number, $valid, $sex, $date, $summ, $digit ) + { + if ( $valid ) { + $this->assertEquals( $digit, Parser::create( $number )->getControlDigit() ); + } + } + + /** + * @dataProvider getData */ - public function testIsValid ( $number, $sex, $date, $valid ) + public function testIsValidNumber ( $number, $valid, $sex, $date, $summ, $digit ) { - $this->assertEquals( Parser::create( $number )->isValid(), $valid ); + $this->assertEquals( Parser::create( $number )->isValidNumber(), $valid ); } /** - * @dataProvider getDataProviders + * @dataProvider getData */ - public function testGetSex ( $number, $sex, $date, $valid ) + public function testGetPersonSex ( $number, $valid, $sex, $date, $summ, $digit ) { - $this->assertEquals( Parser::create( $number )->getSex(), $sex ); - if ( $sex === 'M' ) { - $this->assertTrue( Parser::create( $number )->isMale() ); - $this->assertFalse( Parser::create( $number )->isFemale() ); + if ( $valid ) { + $this->assertEquals( Parser::create( $number )->getPersonSex(), $sex ); + if ( $sex === Parser::SEX_MALE ) { + $this->assertTrue( Parser::create( $number )->isPersonMale() ); + $this->assertFalse( Parser::create( $number )->isPersonFemale() ); + } else { + $this->assertFalse( Parser::create( $number )->isPersonMale() ); + $this->assertTrue( Parser::create( $number )->isPersonFemale() ); + } } else { - $this->assertFalse( Parser::create( $number )->isMale() ); - $this->assertTrue( Parser::create( $number )->isFemale() ); + $this->assertEquals( Parser::create( $number )->getPersonSex(), null ); } } /** - * @dataProvider getDataProviders + * @dataProvider getData */ - public function testGetAge ( $number, $sex, $date, $valid ) + public function testGetPersonAge ( $number, $valid, $sex, $date, $summ, $digit ) { + $datetime = new DateTime( $date . ' 00:00:00', new DateTimeZone( 'UTC' ) ); - $this->assertEquals( - Parser::create( $number )->getAge(), - (int) ( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) )->modify( 'midnight' )->diff( $datetime )->y - ); + if ( $valid ) { + $this->assertEquals( + Parser::create( $number )->getPersonAge(), + (int) ( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) )->modify( 'midnight' )->diff( $datetime )->y + ); + $this->assertEquals( + Parser::create( $number )->getPersonAge( ( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) )->modify( 'midnight -1 year' ) ), + (int) ( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) )->modify( 'midnight -1 year' )->diff( $datetime )->y + ); + } else { + $this->assertEquals( + Parser::create( $number )->getPersonAge(), + null + ); + $this->assertEquals( + Parser::create( $number )->getPersonAge( ( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) )->modify( 'midnight -1 year' ) ), + null + ); + } + } + + /** + * @dataProvider getData + */ + public function testGetPersonBirthDatetime ( $number, $valid, $sex, $date, $summ, $digit ) + { + if ( $valid ) { + $this->assertEquals( Parser::create( $number )->getPersonBirthDatetime()->format( 'Y-m-d' ), $date ); + } else { + $this->assertEquals( Parser::create( $number )->getPersonBirthDatetime(), null ); + } } /** - * @dataProvider getDataProviders + * @dataProvider getData */ - public function testGetBirthDatetime ( $number, $sex, $date, $valid ) + public function testGetPersonBirth ( $number, $valid, $sex, $date, $summ, $digit ) { - $this->assertEquals( Parser::create( $number )->getBirthDatetime()->format( 'Y-m-d' ), $date ); + if ( $valid ) { + $this->assertEquals( Parser::create( $number )->getPersonBirth(), $date ); + $this->assertEquals( Parser::create( $number )->getPersonBirth( 'Y-m-d' ), $date ); + } else { + $this->assertEquals( Parser::create( $number )->getPersonBirth(), null ); + } } } From ad038f45f41c550d7f042d8d36656dedc2d7d692 Mon Sep 17 00:00:00 2001 From: Vitaliy IIIFX Khomenko Date: Sun, 4 Dec 2016 14:56:01 +0200 Subject: [PATCH 2/2] + --- README.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1fb9bf1..2ae54bc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Identification Number: Ukraine -Парсер идентификационного номера налогоплательщика Украины +Идентификационный номер налогоплательщика Украины. Парсер и генератор ИНН. [![SensioLabsInsight](https://insight.sensiolabs.com/projects/746be262-9725-4d22-9ce1-e7eb07dc4858/big.png)](https://insight.sensiolabs.com/projects/746be262-9725-4d22-9ce1-e7eb07dc4858) @@ -17,19 +17,61 @@ $ composer require "iiifx-production/ukraine-identification-number:0.*" ## Использование +Парсер ИНН: + ``` php use iiifx\Identification\Ukraine\Parser; -# Создаем парсер ИНН -$parser = Parser::create( '0123456789' ); +# Номер ИНН +$number = '2245134075'; + +# Создаем парсер +$parser = Parser::create( $number ); +# Или так +$parser = new Parser( $number ); + +# Проверяем правильность ИНН +if ( $parser->isValidNumber() ) { + + $parser->getNumber(); # 2245134075 + + # Определяем пол владельца ИНН + $parser->getPersonSex(); # Parser::SEX_MALE + $parser->isPersonMale(); # true + $parser->isPersonFemale(); # false + + # Определяем возраст и дату рождения + $parser->getPersonAge(); # 55 + $parser->getPersonBirth( 'Y-m-d' ); # 1961-06-20 + $parser->getPersonBirthDatetime()->format( 'd.m.Y H:i:s' ); # 20.06.1961 00:00:00 + + # Контрольная сумма и число + $parser->getControlSumm(); # 192 + $parser->getControlDigit(); # 5 +} +``` + +Генератор ИНН: + +``` php +use iiifx\Identification\Ukraine\Builder; + +# Создаем генератор +$builder = new Builder(); +# Или вот так +$builder = Builder::create( Builder::SEX_MALE, new DateTime( '2010-05-12' ) ); + +# Указывам пол +$builder->setPersonSex( Builder::SEX_MALE ); +$builder->setPersonMale(); +$builder->setPersonFemale(); + +# Указываем возраст +$builder->setPersonAge( 55 ); +$builder->setPersonBirthDatetime( new DateTime( '1962-11-03' ) ); -# Получаем все данные -$parser->getNumber(); # '0123456789' -$parser->isValid(); # true -$parser->getSex(); # 'M' -$parser->isMale(); # true -$parser->getAge(); # 32 -$parser->getBirthDatetime()->format( 'd.m.Y' ); # '02.05.1984' +# Генерируем ИНН +$builder->createNumber(); # 2295209520 ``` ## Тесты