comparison vendor/symfony/validator/Constraints/IsbnValidator.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 129ea1e6d783
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Symfony\Component\Validator\Constraints;
13
14 use Symfony\Component\Validator\Constraint;
15 use Symfony\Component\Validator\ConstraintValidator;
16 use Symfony\Component\Validator\Exception\UnexpectedTypeException;
17
18 /**
19 * Validates whether the value is a valid ISBN-10 or ISBN-13.
20 *
21 * @author The Whole Life To Learn <thewholelifetolearn@gmail.com>
22 * @author Manuel Reinhard <manu@sprain.ch>
23 * @author Bernhard Schussek <bschussek@gmail.com>
24 *
25 * @see https://en.wikipedia.org/wiki/Isbn
26 */
27 class IsbnValidator extends ConstraintValidator
28 {
29 /**
30 * {@inheritdoc}
31 */
32 public function validate($value, Constraint $constraint)
33 {
34 if (!$constraint instanceof Isbn) {
35 throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Isbn');
36 }
37
38 if (null === $value || '' === $value) {
39 return;
40 }
41
42 if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) {
43 throw new UnexpectedTypeException($value, 'string');
44 }
45
46 $value = (string) $value;
47 $canonical = str_replace('-', '', $value);
48
49 // Explicitly validate against ISBN-10
50 if ('isbn10' === $constraint->type) {
51 if (true !== ($code = $this->validateIsbn10($canonical))) {
52 $this->context->buildViolation($this->getMessage($constraint, $constraint->type))
53 ->setParameter('{{ value }}', $this->formatValue($value))
54 ->setCode($code)
55 ->addViolation();
56 }
57
58 return;
59 }
60
61 // Explicitly validate against ISBN-13
62 if ('isbn13' === $constraint->type) {
63 if (true !== ($code = $this->validateIsbn13($canonical))) {
64 $this->context->buildViolation($this->getMessage($constraint, $constraint->type))
65 ->setParameter('{{ value }}', $this->formatValue($value))
66 ->setCode($code)
67 ->addViolation();
68 }
69
70 return;
71 }
72
73 // Try both ISBNs
74
75 // First, try ISBN-10
76 $code = $this->validateIsbn10($canonical);
77
78 // The ISBN can only be an ISBN-13 if the value was too long for ISBN-10
79 if (Isbn::TOO_LONG_ERROR === $code) {
80 // Try ISBN-13 now
81 $code = $this->validateIsbn13($canonical);
82
83 // If too short, this means we have 11 or 12 digits
84 if (Isbn::TOO_SHORT_ERROR === $code) {
85 $code = Isbn::TYPE_NOT_RECOGNIZED_ERROR;
86 }
87 }
88
89 if (true !== $code) {
90 $this->context->buildViolation($this->getMessage($constraint))
91 ->setParameter('{{ value }}', $this->formatValue($value))
92 ->setCode($code)
93 ->addViolation();
94 }
95 }
96
97 protected function validateIsbn10($isbn)
98 {
99 // Choose an algorithm so that ERROR_INVALID_CHARACTERS is preferred
100 // over ERROR_TOO_SHORT/ERROR_TOO_LONG
101 // Otherwise "0-45122-5244" passes, but "0-45122_5244" reports
102 // "too long"
103
104 // Error priority:
105 // 1. ERROR_INVALID_CHARACTERS
106 // 2. ERROR_TOO_SHORT/ERROR_TOO_LONG
107 // 3. ERROR_CHECKSUM_FAILED
108
109 $checkSum = 0;
110
111 for ($i = 0; $i < 10; ++$i) {
112 // If we test the length before the loop, we get an ERROR_TOO_SHORT
113 // when actually an ERROR_INVALID_CHARACTERS is wanted, e.g. for
114 // "0-45122_5244" (typo)
115 if (!isset($isbn[$i])) {
116 return Isbn::TOO_SHORT_ERROR;
117 }
118
119 if ('X' === $isbn[$i]) {
120 $digit = 10;
121 } elseif (ctype_digit($isbn[$i])) {
122 $digit = $isbn[$i];
123 } else {
124 return Isbn::INVALID_CHARACTERS_ERROR;
125 }
126
127 $checkSum += $digit * (10 - $i);
128 }
129
130 if (isset($isbn[$i])) {
131 return Isbn::TOO_LONG_ERROR;
132 }
133
134 return 0 === $checkSum % 11 ? true : Isbn::CHECKSUM_FAILED_ERROR;
135 }
136
137 protected function validateIsbn13($isbn)
138 {
139 // Error priority:
140 // 1. ERROR_INVALID_CHARACTERS
141 // 2. ERROR_TOO_SHORT/ERROR_TOO_LONG
142 // 3. ERROR_CHECKSUM_FAILED
143
144 if (!ctype_digit($isbn)) {
145 return Isbn::INVALID_CHARACTERS_ERROR;
146 }
147
148 $length = strlen($isbn);
149
150 if ($length < 13) {
151 return Isbn::TOO_SHORT_ERROR;
152 }
153
154 if ($length > 13) {
155 return Isbn::TOO_LONG_ERROR;
156 }
157
158 $checkSum = 0;
159
160 for ($i = 0; $i < 13; $i += 2) {
161 $checkSum += $isbn[$i];
162 }
163
164 for ($i = 1; $i < 12; $i += 2) {
165 $checkSum += $isbn[$i]
166 * 3;
167 }
168
169 return 0 === $checkSum % 10 ? true : Isbn::CHECKSUM_FAILED_ERROR;
170 }
171
172 protected function getMessage($constraint, $type = null)
173 {
174 if (null !== $constraint->message) {
175 return $constraint->message;
176 } elseif ('isbn10' === $type) {
177 return $constraint->isbn10Message;
178 } elseif ('isbn13' === $type) {
179 return $constraint->isbn13Message;
180 }
181
182 return $constraint->bothIsbnMessage;
183 }
184 }