diff --git a/src/Symfony/Component/Validator/Constraints/Luhn.php b/src/Symfony/Component/Validator/Constraints/Luhn.php new file mode 100644 index 0000000000..f376045fc7 --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/Luhn.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * Metadata for the LuhnValidator. + * + * @Annotation + */ +class Luhn extends Constraint +{ + public $message = 'Invalid card number'; +} diff --git a/src/Symfony/Component/Validator/Constraints/LuhnValidator.php b/src/Symfony/Component/Validator/Constraints/LuhnValidator.php new file mode 100644 index 0000000000..1ae8b39948 --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/LuhnValidator.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; + +/** + * Validates a PAN using the LUHN Algorithm + * + * For a list of example card numbers that are used to test this + * class, please see the LuhnValidatorTest class. + * + * @see http://en.wikipedia.org/wiki/Luhn_algorithm + * @author Tim Nagel + * @author Greg Knapp http://gregk.me/2011/php-implementation-of-bank-card-luhn-algorithm/ + */ +class LuhnValidator extends ConstraintValidator +{ + /** + * Validates a creditcard number with the Luhn algorithm. + * + * @param mixed $value + * @param Constraint $constraint + */ + public function validate($value, Constraint $constraint) + { + if (null === $value || '' === $value) { + return; + } + + if (!is_numeric($value)) { + $this->context->addViolation($constraint->message); + + return; + } + + $length = strlen($value); + $oddLength = $length % 2; + for ($sum = 0, $i = $length - 1; $i >= 0; $i--) { + $digit = (int) $value[$i]; + $sum += (($i % 2) === $oddLength) ? array_sum(str_split($digit * 2)) : $digit; + } + + if (($sum % 10) !== 0) { + $this->context->addViolation($constraint->message); + } + } +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LuhnValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LuhnValidatorTest.php new file mode 100644 index 0000000000..3c05778639 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/LuhnValidatorTest.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Constraints; + +use Symfony\Component\Validator\Constraints\Luhn; +use Symfony\Component\Validator\Constraints\LuhnValidator; + +class LuhnValidatorTest extends \PHPUnit_Framework_TestCase +{ + protected $context; + protected $validator; + + protected function setUp() + { + $this->context = $this->getMock('Symfony\Component\Validator\ExecutionContext', array(), array(), '', false); + $this->validator = new LuhnValidator(); + $this->validator->initialize($this->context); + } + + protected function tearDown() + { + $this->context = null; + $this->validator = null; + } + + public function testNullIsValid() + { + $this->context->expects($this->never()) + ->method('addViolation'); + + $this->validator->validate(null, new Luhn()); + } + + public function testEmptyStringIsValid() + { + $this->context->expects($this->never()) + ->method('addViolation'); + + $this->validator->validate('', new Luhn()); + } + + /** + * @dataProvider getValidNumbers + */ + public function testValidNumbers($number) + { + $this->context->expects($this->never()) + ->method('addViolation'); + + $this->validator->validate($number, new Luhn()); + } + + public function getValidNumbers() + { + return array( + array('42424242424242424242'), + array('378282246310005'), + array('371449635398431'), + array('378734493671000'), + array('5610591081018250'), + array('30569309025904'), + array('38520000023237'), + array('6011111111111117'), + array('6011000990139424'), + array('3530111333300000'), + array('3566002020360505'), + array('5555555555554444'), + array('5105105105105100'), + array('4111111111111111'), + array('4012888888881881'), + array('4222222222222'), + array('5019717010103742'), + array('6331101999990016'), + ); + } + + /** + * @dataProvider getInvalidNumbers + */ + public function testInvalidNumbers($number) + { + $constraint = new Luhn(); + + $this->context->expects($this->once()) + ->method('addViolation') + ->with($constraint->message); + + $this->validator->validate($number, $constraint); + } + + public function getInvalidNumbers() + { + return array( + array('1234567812345678'), + array('4222222222222222'), + ); + } +}