# How to Validate a South African ID Number With PHP

If you have a website or app that requires users to submit their South African identity number, you will definitely want to make sure it’s valid. The South African ID is validated by matching the following criteria.

- The total number of digits must be 13.
- The format is [YYMMDD] [GGGG] [C] [A] [Z]
- [YYMMDD] for the ID holder’s date of birth
- [GGGG] for ID holder’s gender. 0000 – 4999 for female and 5000 – 9999 for male.
- [C] is their citizenship status. 0 is a South African Citizen and 1 is a permanent resident.
- [A] can be any number but, at the time of writing this post, is usually an 8.
- [Z] is a check digit which uses the Luhn Algorithm to validate the rest of the ID number.

#### Prerequisites

- A basic understanding of PHP

Since the check digit is the tricky part, we’ll leave that for last, so let’s begin with our basic function. I’m going to use the example ID number: 8001015009087.

```
<?php
function verify_id_number($id_number, $gender = '', $foreigner = 0) {
$validated = false;
// Validation code will go here
return $validated;
}
```

The **verify_id_number()** function will accept three parameters: a required (**$id_number**) and another two, **$gender** and **$foreigner**, which are optional. If you prefer to make them required, you may remove their values. You may be wondering why I declared the last variable as *$foreigner* instead of *$citizen*, but that will make sense when we get to validating citizenship.

Inside the function, I’ve declared a **$validated** variable and given it a value of * false*. When the function is called, it will return

**$validated**, which at this time is false.

## Validate the Length of the ID Number

The first check is to ensure the ID number is exactly 13 digits long using PHP’s built-in ** strlen()** function. We also know that the ID number has to be numbers so we’ll validate that as well using

**.**

*is_numeric()*```
<?php
if (is_numeric($id_number) && strlen($id_number) === 13) {
$errors = false;
// The rest of the conditional statements will go in here
}
```

Inside the if statement, I’ve declared the **$errors** variable which is set to *false*. When we find an error, we’ll set it to true and by the end of all the checks, if it’s still *false* and hasn’t been changed by an error, we’ll make sure **$validated** is set to true and pass validation.

## Convert the ID Number to an Array

For the next parts, I’m going to turn the ID number into an array. It will make it easier to select the parts we need. To do this, we can use another built-in PHP function called ** str_split()**.

```
<?php
$num_array = str_split($id_number);
```

If we print_r(), we’ll get our array with indices numbered 0 through 12. Like this:

Array (

[0] => 8

[1] => 0

[2] => 0

[3] => 1

[4] => 0

[5] => 1

[6] => 5

[7] => 0

[8] => 0

[9] => 9

[10] => 0

[11] => 8

[12] => 7

)

Now we can access the indices much easier.

## Validate The ID Holder’s Date of Birth

To ensure the date of birth is valid, we can use some general knowledge.

- We know the maximum amount of days in a month have to be 31 and none of them can be 0.
- We know there are only 12 months in a year and none of them can be 0.

I’m not going to validate the year. Because the ID number is only two digits, this could pose a problem for ID holders aged 100 or more. Maybe your site or app only allows users between a specific age range in which case you may want to validate the year.

```
<?php
// Validate the day and month
$id_month = $num_array[2] . $num_array[3];
$id_day = $num_array[4] . $num_array[5];
if ( $id_month < 1 || $id_month > 12) {
$errors = true;
}
if ( $id_day < 1 || $id_day > 31) {
$errors = true;
}
```

The above code concatenated the array indices 2 and 3 to define the **$id_month** while the **$id_day** is a concatenation of indices 4 and 5 ie. *80* **[01] [01**] *5009087* in our example ID number.

If the month is not a number between 1 and 12, or if the day is not between 1 and 31, validation fails.

## Validate Expected Gender vs Submitted Gender

In **$num_array**, indices 6 to 9 are used to validate gender, but we only need to use index 6. In the following code, I’m using a ternary if statement to assign a ‘*male*‘ or ‘*female*‘ value. ‘Male’ if the value is 5 or more, ‘female’ if not.

With **$id_gender** defined, I check that there is a value set in the function’s **$gender** parameter, and if there is, I check that it matches **$id_gender**.

```
<?php
// Validate gender
$id_gender = $num_array[6] >= 5 ? 'male' : 'female';
if ($gender && strtolower($gender) !== $id_gender) {
$errors = true;
}
```

If it’s not a match, **$errors** is set to *true* and validation fails.

## Validate South African Citizenship

The South African ID number uses the number 0 to classify a South African citizen and the number 1 to classify the ID holder as a permanent resident while in programming, 0 is false and 1 is true. For that reason, I’ve chosen to use the **$foreigner** variable. ie. $foreigner = 0 (false) means that this person is a citizen.

**$id_foreigner** inherits the value set in the 11th position (index 10) of our **$num_array**.

Next, we check whether **$foreigner** or **$id_foreigner** are true. (Note: I’m assuming that the function’s **$foreigner** parameter will get data submitted via a checkbox. If the checkbox is checked, the submitted value will be 1. In PHP, any value other than 0 is true). If either **$foreigner** or **$id_foreigner** are true, we also check that they match. If they do, it will validate. Notice, I’ve explicitly converted the strings to integers using ** (int)** to ensure they are numbers. We don’t need an else statement. If both values are 0 (empty / false) they match by default.

```
<?php
// Validate citizenship
// citizenship as per id number
$id_foreigner = $num_array[10];
// citizenship as per submission
if ( ( $foreigner || $id_foreigner ) && (int)$foreigner !== (int)$id_foreigner ) {
$errors = true;
}
```

Now for the more tricky part…

## Validating the ID Number Check Digit With PHP

The validation process for the South African ID check digit contains quite a few rules. It uses the Luhn Algotrithm and goes something like this:

- All digits in odd positions (excluding the check digit) must be added together.
- From our example ID number:
**[8]**0**[0]**1**[0]**1**[5]**0**[0]**9**[0]**8 7 - 8 + 0 + 0 + 5 + 0 + 0 which equals 13.

- From our example ID number:
- All digits in even positions must be concatenated to form a 6 digit number. This 6 digit number must then be multiplied by 2.
- From our example ID number: 8
**[0]**0**[1]**0**[1]**5**[0]**0**[9]**0**[8]**7 - 011098 x 2 equals 22196.

- From our example ID number: 8
- Add all the digits from the result of 2. ie (22196).
- 2 + 2 + 1 + 9 + 6 = 20.

- Add the answer from 1 to the answer in 3. (13 and 20).
- 13 + 20 = 33.

- Grab the answer from 4 and subtract the last digit from 10. 3
**[3]**.- 10 – 3 = 7.

- If the answer from 5 is one digit, that should be the same as the check digit. If it’s more than one digit, the last digit of the answer from 5 should the same as the check digit.

So let’s code this mofo!

### Move All Digits in Even and Odd Positions Into Separate Arrays

```
<?php
// Declare the arrays
$even_digits = array();
$odd_digits = array();
// Loop through modified $num_array, storing the keys and their values in the above arrays
foreach ( $num_array as $index => $digit) {
if ($index === 0 || $index % 2 === 0) {
$odd_digits[] = $digit;
}
else {
$even_digits[] = $digit;
}
}
```

Here, I’ve declared two arrays to store the even and odd numbers, (**$even_digits** and **$odd_digits**). Then we loop through the digits in the ID number (**$num_array**) and if the digit’s key is 0 or divisible by 2 with no remainder (**%**), it’s in an odd position so we push it to the **$odd_digits** array. (Because arrays start at position 0, we have to include that position specifically, and all subsequent keys with an odd number are actually in even positions. For example, array[1] is in position 2 and array[2] is in position 3 and so on).

### Pop the Check Digit and Add the Odds

Next up, I’m going to use a built-in PHP function to take care of two things. Remember, the check digit must be excluded when we add up the odd digits, so we first we need to remove it from the odds array. While we’re at it, we can store that check digit we’ve popped in a variable. PHP’s ** array_pop()** can do both of these at once.

```
<?php
// use array pop to remove the last digit from $odd_digits and store it in $check_digit
$check_digit = array_pop($odd_digits);
```

Now we can add the remaining digits in the modified **$odd_digits** array using the built-in PHP function, ** array_sum()**.

```
<?php
//All digits in odd positions (excluding the check digit) must be added together.
$added_odds = array_sum($odd_digits);
```

### Concatenate the Evens to Create a 6 Digit Number and Multiply it by 2

To concatenate the numbers into a string, we can use the PHP ** implode()** function.

**implode()**accepts two parameters which include a string that separates each value and the array to search. I don’t want the numbers separated, so the first parameter is empty. After that I multiply the

**$concatenated_evens**by 2.

```
<?php
//All digits in even positions must be concatenated to form a 6 digit number.
$concatenated_evens = implode('', $even_digits);
//This 6 digit number must then be multiplied by 2.
$evensx2 = $concatenated_evens * 2;
```

### Add The Digits In The Concatenated Evens

Similarly to what we did with the odd numbers, I’m going to convert this new number ($evensx2) into an array using **str_split()**, then use **array_sum()** so as to calculate the total of the all the numbers. This time I’ll do it in one line, like so:

```
<?php
// Add all the numbers produced from the even numbers x 2
$added_evens = array_sum( str_split($evensx2) );
```

### Add the Sum of Odds to the Sum of the Evens

**$added_evens** isn’t really the sum of evens, but for simplicity, we’ll call it that. So let’s tally up the numbers and finalise everything.

```
<?php
// get the last digit of the $sum
$last_digit = substr($sum, -1);
/* 10 - $last_digit
* $verify_check_digit = 10 - (int)$last_digit; (Will break if $last_digit = 0)
* Or as suggested by Ruan Luies
* verify check digit is the resulting remainder of 10 minus the last digit divided by 10
*/
$verify_check_digit = (10 – (int)$last_digit) % 10;
// test expected last digit against the last digit in $id_number submitted
if ((int)$verify_check_digit !== (int)$check_digit) {
$errors = true;
}
// if errors haven't been set to true by any one of the checks, we can change verified to true;
if (!$errors) {
$validated = true;
}
```

As you can see from the above code, I’ve used the PHP ** substr()** function which returns a part of a string. Using -1, we go back one space to get the last digit from our

**$sum**and store that in

**$last_digit**.

After that, ~~we subtract that last digit from 10~~ or as suggested by **Ruan Luies**, instead of simply subtracting the last digit, we should check the remainder of 10 minus the last digit divided by 10. This ensures that if the last digit is 0, we don’t end up with 10 again, in which case validation will fail. Thanks Ruan.

Next we check that the expected last digit calculated from the other numbers in **$id_number** match the *actual* last digit in **$id_number**. If they don’t match, **$errors** will once again be set to *true*. Notice that I’ve explicitly converted these numbers to integers using **(int) **to prevent any possible errors.

Finally, if there aren’t any errors, the **$errors** variable will remain unchanged and will still be *false* so we can change **$v**alidated to true.

## Full PHP Function to Validate the South African ID Number

```
<?php
function verify_id_number($id_number, $gender = '', $foreigner = 0) {
$validated = false;
if (is_numeric($id_number) && strlen($id_number) === 13) {
$errors = false;
$num_array = str_split($id_number);
// Validate the day and month
$id_month = $num_array[2] . $num_array[3];
$id_day = $num_array[4] . $num_array[5];
if ( $id_month < 1 || $id_month > 12) {
$errors = true;
}
if ( $id_day < 1 || $id_day > 31) {
$errors = true;
}
// Validate gender
$id_gender = $num_array[6] >= 5 ? 'male' : 'female';
if ($gender && strtolower($gender) !== $id_gender) {
$errors = true;
}
// Validate citizenship
// citizenship as per id number
$id_foreigner = $num_array[10];
// citizenship as per submission
if ( ( $foreigner || $id_foreigner ) && (int)$foreigner !== (int)$id_foreigner ) {
$errors = true;
}
/**********************************
Check Digit Verification
**********************************/
// Declare the arrays
$even_digits = array();
$odd_digits = array();
// Loop through modified $num_array, storing the keys and their values in the above arrays
foreach ( $num_array as $index => $digit) {
if ($index === 0 || $index % 2 === 0) {
$odd_digits[] = $digit;
}
else {
$even_digits[] = $digit;
}
}
// use array pop to remove the last digit from $odd_digits and store it in $check_digit
$check_digit = array_pop($odd_digits);
//All digits in odd positions (excluding the check digit) must be added together.
$added_odds = array_sum($odd_digits);
//All digits in even positions must be concatenated to form a 6 digit number.
$concatenated_evens = implode('', $even_digits);
//This 6 digit number must then be multiplied by 2.
$evensx2 = $concatenated_evens * 2;
// Add all the numbers produced from the even numbers x 2
$added_evens = array_sum( str_split($evensx2) );
$sum = $added_odds + $added_evens;
// get the last digit of the $sum
$last_digit = substr($sum, -1);
/* 10 - $last_digit
* $verify_check_digit = 10 - (int)$last_digit; (Will break if $last_digit = 0)
* Edit suggested by Ruan Luies
* verify check digit is the resulting remainder of
* 10 minus the last digit divided by 10
*/
$verify_check_digit = (10 – (int)$last_digit) % 10;
// test expected last digit against the last digit in $id_number submitted
if ((int)$verify_check_digit !== (int)$check_digit) {
$errors = true;
}
// if errors haven't been set to true by any one of the checks, we can change verified to true;
if (!$errors) {
$validated = true;
}
}
return $validated;
}
```

I built a WordPress plugin using this code which available to download on the WordPress plug repository. You can also view this plugin on GitHub.

## Where To Use This Code

Copy and paste the function into the script that handles your form submission and insert the necessary POST/GET variables as parameters.
As an example:
**if (verify_id_number($_POST[‘id-number’], $_POST[‘gender’], $_POST[‘gender’)**
**{ // Do something if ID number is validated }**
**else { // Do something if ID number not validated }**

Is this still valid in 2024? Please let me know in the comments below.