There are multiple ways to generate a very secure password, but I only found a small set of those is also user friendly. Meaning there are no ambiguous characters such as i, l, 1, o, 0, etc. The best version I found so far is the following:

public function generateStrongPassword($length = 12): string
{
    $sets = [
        'abcdefghjkmnpqrstuvwxyz',
        'ABCDEFGHJKMNPQRSTUVWXYZ',
        '23456789',
        '!@#$%&*?',
    ];

    $all = '';
    $password = '';

    foreach ($sets as $set) {
        $password .= $set[array_rand(str_split($set))];
        $all .= $set;
    }

    $all = str_split($all);
    for ($i = 0; $i < $length - count($sets); ++$i) {
        $password .= $all[array_rand($all)];
    }

    return str_shuffle($password);
}

You can simple add or remove characters you want to have in there. After a certain point the number of possible characters isn't the key to the security, but the length of the password is. So make sure to make it long enough.

The function is a variation of this gist from Tyler Hall. Many thanks to him for it.