Losowy łańcuch znaków (string) w PHP

random_string

Kolejny wpis dotyczący języka PHP i zarazem kolejny, który dotyczy operacji na łańcuchach znaków (string) w tymże języku. Tym razem będzie to prosta klasa, która pozwoli nam wygenerować losowy łańcuch znaków, który może nam posłużyć jako kod do weryfikacji, hash, czy losowo wygenerowane hasło.

W tym celu posłużymy się statyczną funkcją generate($length), która przyjmować będzie tylko jeden argument $length, który odpowiadać będzie za długość generowanego ciągu znaków.

public static function generate($length) {
    // TODO
}

Poza tym – musimy sobie stworzyć łańcuch znaków $chars, który zawierać będzie wszystkie dostępne znaki jakie będą mogły wystąpić w wygenerowanym stringu.

private $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

Ostatecznie wykorzystamy pętlę for oraz funkcję rand(), która losowo wybierze nam poszczególne wartości ze zdefiniowanego wcześniej łańcucha znaków $chars.

$size = strlen($this->chars);
for ($i = 0; $i < $length; $i++) {
	$randomString .= $this->chars[rand(0, $size - 1)];
}

Cała klasa prezentuje się następująco:

<?php

class RandomString {

	private $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

	public static function generate($length) {
		$randomString = '';
		$size = strlen($this->chars);
		for ($i = 0; $i < $length; $i++) {
			$randomString .= $this->chars[rand(0, $size - 1)];
		}

		return $randomString;
	}

}

Użycie:

$randomString = RandomString::generate(5);
// $randomString = PAozU

Lepsze formatowanie stringów w PHP

php-string

Formatowanie łańcuchów znaków w języku PHP często jest mało użyteczne, a sam zapis takiego kodu niezbyt przejrzysty.

W Pythonie realizowane jest to znacznie lepiej i przykładowy kod takiego rozwiązania wygląda tak:

print "Hello %(name)s. Your %(name)s has just been created!" % { 'name' : 'world' }
# Hello world. Your world has just been created!

W powyższym przykładzie widać, że argumenty, które mają zostać zamienione na podany przez nas łańcuch znaków są nazwane i wielokrotne ich występowanie nie przysparza żadnego problemu.

W PHP by osiągnąć podobny efekt musielibyśmy napisać coś takiego:

sprintf("Hello %s. Your %s has just been created!", 'world', 'world');
// Hello world. Your world has just been created!

Zgodzicie się pewnie ze mną, że gdyby powyższy przykład zawierał kilka zdań więcej i wielokrotne występowanie różnych argumentów, to tworzenie takiego kodu oraz jego czytelność pozostawiałyby wiele do życzenia, a na pewno sprawiały trudności podczas późniejszej modyfikacji.

Możemy wprawdzie użyć takiej konstrukcji:

printf('Hello %1$s. Your %1$s has just been created!', 'world');

.., ale tutaj także nie mamy argumentów nazwanych.

Jednak takie rozwiązanie jest osiągalne i przykład takiego kodu znalazłem w oficjalnej dokumentacji PHP na stronie: http://www.php.net/manual/en/function.vsprintf.php

W tym wypadku wykorzystana zostanie funkcja vsprintf, która różni się tym od funkcji sprintf, że zamiast podawania bezpośredniego argumentów po przecinku, przyjmuje ona tablicę takich argumentów.

function dsprintf() {
  $data = func_get_args(); // pobieramy wszystkie argumenty przekazane do funkcji
  $string = array_shift($data); // nasz lancuch znakow jest pierwszym argumentem
  if (is_array(func_get_arg(1))) { // jezeli drugim argumentem jest tablica - uzywamy jej
    $data = func_get_arg(1);
  }
  $used_keys = array();
  // pobieramy argumenty z lancucha znakow i przekazujemy je do naszej kolejnej funkcji
  $string = preg_replace('/\%\((.*?)\)(.)/e',
    'dsprintfMatch(\\'$1\\',\\'$2\\',\\$data,$used_keys)', $string);
  $data = array_diff_key($data,$used_keys); // diff the data with the used_keys
  return vsprintf($string,$data); // yeah!
}

function dsprintfMatch($m1,$m2,&$data,&$used_keys) {
  if (isset($data[$m1])) { // sprawdzamy czy dany klucz istnieje w przekazanej tablicy argumentow
    $str = $data[$m1];
    $used_keys[$m1] = $m1; // nie usuwamy wystapienia - moze wystepowac wiele razy
    return sprintf("%".$m2,$str); // uzywamy sprintf na lancuchu znakow, wiec %s lub %d dzialaja tak jak powinny
  } else {
    return "%".$m2; // w przeciwnym wypadku uzywamy %s lub %d - w zaleznosci od wykorzystania
  }
}

$str = <<<HITHERE
Hello, %(firstName)s, I know your favorite funcion is %(functionName)s.
HITHERE;

$dataArray = array(
 'firstName'   => 'PHP',
 'functionName' => 'var_dump()',
);
echo dsprintf($str, $dataArray);
// Hello, PHP, I know your favorite function is var_dump().