How to use regex validation in Laravel for uppercase letters, numbers and square brackets and then list as a postcode range?

Viewed 900

I want to convert the user input to a list of postcodes, e.g.

  1. If the user types SW[1-3] return a list of SW1, SW2, SW3.
  2. I also need it to work alphabetically, if the user inputs LD1[A-D] return a list of LD1A, LD1B, LD1C, LD1D.
  3. If the user doesn't use square brackets and just does LD1 then it should just return LD1 in the list.

This is what I've tried so far but I can't get the validation to work and I'm not sure I have the best method for creating the list.

public function store(Request $request) {
    $request->validate([
        'postcode' => 'required|string|regex:/^[A-Z0-9][\[A-Z\-0-9\]]*$/u',
    ]);

    $postcode_input = $request->postcode;
    $postcode_list = [];

    // check for []'s
    $match = preg_match('/\[[^\]]*\]/', $postcode_input, $matches);
    if($match) {
        $letters = explode("[", $postcode_input)[0] ?? false;
        $numbers = explode("-", str_replace( array('[',']') , ''  , $matches )[0]) ?? false;

        if($letters && $numbers[0] && $numbers[1]) {
            foreach (range($numbers[0], $numbers[1]) as $number) {
                $postcode_list[] = $letters . $number;
            }
        }
    } else {
        $postcode_list[] = $postcode_input;   
    }

    if(count($postcode_list) > 0) {
        foreach($postcode_list as $pc) {
            $pc = preg_replace("/\s+/", "", $pc);
            echo $pc . "<br>";
        }
    }
}
1 Answers

Assuming that the range for [A-Z] is a single uppercase character, you might use a pattern with capture groups and a branch reset to access the same group number in the alternation.

^([A-Z\d]+)\[(?|(\d+)-(\d+)|([A-Z])-([A-Z]))]|([A-Z\d]+)$
  • ^ Start of string
  • ([A-Z\d]+) Capture group 1 Match 1+ times A-Z or a digit
  • \[ Match [
  • (?| Branch reset group
    • (\d+)-(\d+) Capture 1+ digits in group 2 and group 3 with - in between
    • | Or
    • ([A-Z])-([A-Z]) Capture a single char A-Z in group 2 and group 3 with - in between
  • ) Close branch reset
  • ] Match ]
  • | Or
  • ([A-Z\d]+) Capture group 4 Match 1+ times A-Z or a digit (in case of only LD1)
  • $ End of string

Regex demo | Php demo

Example code, using the value from group 2 and group 3 for the range:

$pattern = "/^([A-Z\d]+)\[(?|(\d+)-(\d+)|([A-Z])-([A-Z]))]|([A-Z\d]+)$/";
$strings = [
    "SW[1-3]",
    "LD1[A-D]",
    "LD1"
];

foreach ($strings as $s) {
    if (preg_match($pattern, $s, $match)) {
        if (array_key_exists(4, $match)) {
            echo $match[4] . PHP_EOL;
            continue;
        }
        foreach (range($match[2], $match[3]) as $m) {
            echo $match[1] . $m . PHP_EOL;
        }
    }
}

Output

SW1
SW2
SW3
LD1A
LD1B
LD1C
LD1D
LD1
Related