Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\Validator\Constraints; Chris@0: Chris@0: use Symfony\Component\Validator\Constraint; Chris@0: use Symfony\Component\Validator\ConstraintValidator; Chris@0: use Symfony\Component\Validator\Exception\UnexpectedTypeException; Chris@0: Chris@0: /** Chris@0: * Validates a PAN using the LUHN Algorithm. Chris@0: * Chris@0: * For a list of example card numbers that are used to test this Chris@0: * class, please see the LuhnValidatorTest class. Chris@0: * Chris@0: * @see http://en.wikipedia.org/wiki/Luhn_algorithm Chris@0: * Chris@0: * @author Tim Nagel Chris@0: * @author Greg Knapp http://gregk.me/2011/php-implementation-of-bank-card-luhn-algorithm/ Chris@0: * @author Bernhard Schussek Chris@0: */ Chris@0: class LuhnValidator extends ConstraintValidator Chris@0: { Chris@0: /** Chris@0: * Validates a credit card number with the Luhn algorithm. Chris@0: * Chris@0: * @param mixed $value Chris@0: * @param Constraint $constraint Chris@0: * Chris@0: * @throws UnexpectedTypeException when the given credit card number is no string Chris@0: */ Chris@0: public function validate($value, Constraint $constraint) Chris@0: { Chris@0: if (!$constraint instanceof Luhn) { Chris@0: throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Luhn'); Chris@0: } Chris@0: Chris@0: if (null === $value || '' === $value) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // Work with strings only, because long numbers are represented as floats Chris@0: // internally and don't work with strlen() Chris@17: if (!\is_string($value) && !(\is_object($value) && method_exists($value, '__toString'))) { Chris@0: throw new UnexpectedTypeException($value, 'string'); Chris@0: } Chris@0: Chris@0: $value = (string) $value; Chris@0: Chris@0: if (!ctype_digit($value)) { Chris@0: $this->context->buildViolation($constraint->message) Chris@0: ->setParameter('{{ value }}', $this->formatValue($value)) Chris@0: ->setCode(Luhn::INVALID_CHARACTERS_ERROR) Chris@0: ->addViolation(); Chris@0: Chris@0: return; Chris@0: } Chris@0: Chris@0: $checkSum = 0; Chris@17: $length = \strlen($value); Chris@0: Chris@0: // Starting with the last digit and walking left, add every second Chris@0: // digit to the check sum Chris@0: // e.g. 7 9 9 2 7 3 9 8 7 1 3 Chris@0: // ^ ^ ^ ^ ^ ^ Chris@0: // = 7 + 9 + 7 + 9 + 7 + 3 Chris@0: for ($i = $length - 1; $i >= 0; $i -= 2) { Chris@0: $checkSum += $value[$i]; Chris@0: } Chris@0: Chris@0: // Starting with the second last digit and walking left, double every Chris@0: // second digit and add it to the check sum Chris@0: // For doubles greater than 9, sum the individual digits Chris@0: // e.g. 7 9 9 2 7 3 9 8 7 1 3 Chris@0: // ^ ^ ^ ^ ^ Chris@0: // = 1+8 + 4 + 6 + 1+6 + 2 Chris@0: for ($i = $length - 2; $i >= 0; $i -= 2) { Chris@0: $checkSum += array_sum(str_split($value[$i] * 2)); Chris@0: } Chris@0: Chris@0: if (0 === $checkSum || 0 !== $checkSum % 10) { Chris@0: $this->context->buildViolation($constraint->message) Chris@0: ->setParameter('{{ value }}', $this->formatValue($value)) Chris@0: ->setCode(Luhn::CHECKSUM_FAILED_ERROR) Chris@0: ->addViolation(); Chris@0: } Chris@0: } Chris@0: }