How to increment letters like numbers in PHP?

I would like to write a function that takes in 3 characters and increments it and returns the newly incremented characters as a string.

I know how to increase a single letter to the next one but how would I know when to increase the second letters and then stop and then increase the first letter again to have a sequential increase?

So if AAA is passed, return AAB. If AAZ is passed return ABA (hard part).

I would appreciate help with the logic and what php functions will be useful to use.

Even better, has some done this already or there is a class available to do this??

Thanks all for any help


Character/string increment works in PHP (though decrement doesn't)

$x = 'AAZ';
$x++;
echo $x;  // 'ABA'

You can do it with the ++ operator.

$i = 'aaz';
$i++;
print $i;

aba

However this implementation has some strange things:

for($i = 'a'; $i < 'z'; $i++) print "$i ";

This will print out letters from a to y.

for($i = 'a'; $i <= 'z'; $i++) print "$i ";

This will print out lettes from a to z and it continues with aa and ends with yz.


As proposed in PHP RFC: Strict operators directive (currently Under Discussion):

Using the increment function on a string will throw a TypeError when strict_operators is enabled.

Whether or not the RFC gets merged, PHP will sooner or later go that direction of adding operator strictness. Therefore, you should not be incrementing strings.

a-z/A-Z ranges

If you know your letters will stay in range a-z/A-Z (not surpass z/Z), you can use the solution that converts letter to ASCII code, increments it, and converts back to letter.

Use ord() a chr():

$letter = 'A';
$letterAscii = ord($letter);
$letterAscii++;
$letter = chr($letterAscii); // 'B'
  1. ord() converts the letter into ASCII num representation
  2. that num representation is incremented
  3. using chr() the number gets converted back to the letter

As discovered in comments, be careful. This iterates ASCII table so from Z (ASCII 90), it does not go to AA, but to [ (ASCII 91).

Going beyond z/Z

If you dare to go further and want z became aa, this is what I came up with:

final class NextLetter
{
    private const ASCII_UPPER_CASE_BOUNDARIES = [65, 91];
    private const ASCII_LOWER_CASE_BOUNDARIES = [97, 123];

    public static function get(string $previous) : string
    {
        $letters = str_split($previous);
        $output = '';
        $increase = true;

        while (! empty($letters)) {
            $letter = array_pop($letters);

            if ($increase) {
                $letterAscii = ord($letter);
                $letterAscii++;
                if ($letterAscii === self::ASCII_UPPER_CASE_BOUNDARIES[1]) {
                    $letterAscii = self::ASCII_UPPER_CASE_BOUNDARIES[0];
                    $increase = true;
                } elseif ($letterAscii === self::ASCII_LOWER_CASE_BOUNDARIES[1]) {
                    $letterAscii = self::ASCII_LOWER_CASE_BOUNDARIES[0];
                    $increase = true;
                } else {
                    $increase = false;
                }

                $letter = chr($letterAscii);
                if ($increase && empty($letters)) {
                    $letter .= $letter;
                }
            }

            $output = $letter . $output;
        }

        return $output;
    }
}

I'm giving you also 100% coverage if you intend to work with it further. It tests against original string incrementation ++:

    /**
     * @dataProvider letterProvider
     */
    public function testIncrementLetter(string $givenLetter) : void
    {
        $expectedValue = $givenLetter;

        self::assertSame(++$expectedValue, NextLetter::get($givenLetter));
    }

    /** 
     * @return iterable<array<string>>
     */
    public function letterProvider() : iterable
    {
        yield ['A'];
        yield ['a'];
        yield ['z'];
        yield ['Z'];
        yield ['aaz'];
        yield ['aaZ'];
        yield ['abz'];
        yield ['abZ'];
    }