Notatki PHP

prywatne zapiski na kamieniu

Flintstone - plikowa "baza danych" w oparciu o pliki tekstowe w PHP.

Flintstone zapisuje w plikach tekstowych następujące typy danych:

  • Strings
  • Integers
  • Floats
  • Arrays
  • Null

Nowa wersja 2.x.x działa dopiero od wersji 5.6+ PHP, więc dla starszych wersji PHP utraciła komfort kompatybilności.

Gdy masz problem z pobraniem paczki przy pomocy composer.json, czasami konieczne jest poprawienie pliku do postaci:

 
 "name": "fire015/flintstone^1.0",

gdzie należy podać numer wersji, by pobranie się udało.

Dla nowszej wersji będzie to na przykład nie wersja 1.0, ale np. 2.1.1:

 
"name": "fire015/flintstone^2.1.1",

Często mając localhost w środowisku Windows oraz Composer'a, możemy nie zdążyć przeczytać w szybko znikającym oknie, dlaczego pobranie paczki z plikami skryptu nie jest możliwe. Prosty patent - piszemy plik bat w którym umieszczamy wywołanie i nim uruchamiamy proces:

 
composer require fire015/flintstone
pause

Wówczas, okno konsoli terminala się nie zamknie i pozwoli dowiedzieć się, dlaczego na przykład nie możemy pobrać paczki, gdy nie uda się to z jakiejś przyczyny.

 

Wracając do bazy danych w oparciu o flat files database. 

Jak działa starsza wersja 1.0? 

Po pobraniu i zainstalowaniu przykładowy kod PHP zapisujący w plikach tekstowych wyglądać może tak:

 
// Set options
// ustalamy folder na pliki w których zapiszemy dane
$options = array('dir' => '/datastore/');
 
// Load the databases
// tworzymy w ustalonym katalogu plik users w którym będą zapisywane dane
$users = Flintstone::load('users', $options);
 
// tworzymy w ustalonym katalogu plik settings w którym będą zapisywane dane
$settings = Flintstone::load('settings', $options);
 
// Set keys
// zapisujemy dane do plików - tworzymy tablice
$users->set('bob', array('email' => 'bob@site.com', 'password' => '123456'));
$users->set('joe', array('email' => 'joe@site.com', 'password' => 'test'));
$settings->set('site_offline', 1);
$settings->set('site_back', 'za 3 dni');
 
// Retrieve keys
// to co zostało zapisane, możemy już odczytać
// odczytamy z pliku w którym są dane users zawartość tablicy $userbob zapisaną tu w plikowej bazie dla klucza tablicy 'bob'
$userbob = $users->get('bob');
 
// z pozyskanej tablicy możemy wydobyć element z klucza email
echo 'Bob, twój adres e-mail to: ' . $userbob['email'];
 
 
// sprawdzamy wartość tokenu 
$offline = $settings->get('site_offline');
 
 
// jeśli nasz token jest o wartości 1
if ($offline == 1) {
    echo 'Przepraszamy, mamy przerwę techniczną<br>';
// odczytujemy z tablicy wartość odpowiednią dla klucza 'site_back'
    echo 'Serwis uruchomiony zostanie ' . $settings->get('site_back');
}
 
// Retrieve all key names
// z tablicy możemy pozyskać same klucze tablicy users
$keys = $users->getKeys(); // returns array
// Array
// (
    // [0] => bob
    // [1] => joe
// )
 
 
// działamy na kluczach wykonując operacje na elementach tablicy, gdzie sięgamy po elementy z tablicy będące e-mailem czy hasłem
 
foreach ($keys as $username) {
    $user = $users->get($username);
    echo $username.', twój email to ' . $user['email'];
    echo $username.', twoje hasło to ' . $user['password'];
}
 
// Delete a key
// z tablicy users kasujemy zawartość określoną kluczem 'joe'
$users->delete('joe');
 
// Flush the database
// kasujemy zawartość tabeli i pliku users
$users->flush();
 
// uruchamiamy autoload dla bazy Flinstone
require 'vendor/autoload.php';
 
use Flintstone/Flintstone;
use Flintstone/Formatter/JsonFormatter;
 
// formatujemy tablicę users jako zapis danych zgodny z JSON 
$users = Flintstone::load('users', array(
    'dir' => __DIR__,
    'formatter' => new JsonFormatter()
));
 

Flintstone::load(string $database, array $options)

- to załaduje bazę danych z opcjami, zwracającymi obiekt do użycia z metodami poniżej:

 

$dir - katalog, gdzie pliki tekstowe bazy danych zostaną zapisane (najlepiej w miejscu, do którego nie ma dostępu przez stronę web)
np.: /path/to/datastore/

$ext (.dat) - rozszerzenie domyślne plików tekstowej bazy danych
np. można ustawić jako pliki .txt

$gzip (false) - użyje gzip do skompresowania bazy
np. ustawiamy jako true


$cache (true) - zapisze rezultat wywołania get() w pamięci
np. false

$formatter (object) - klasa formatera używana do kodowania/dekodowania danych
np. new JsonFormatter()


$swap_memory_limit (1048576) - ilość pamięci do zajęcia przed zapisem do pliku tymczasowego
np.: 0

get(string $key) - pozyska nazwę kluczy. Zwróci false jeśli nie istnieją.

set(string $key, mixed $data) - zapisze dane w tabeli określone poprzez nazwę klucza. Dane mogą być jako string, integer, float lub array. Zwraca true lub wyrzuci wyjątek błedów gdy zapis nie będzie możliwy.

replace(string $key, mixed $data) - zastąpi dane dla wybranego klucza tabeli. Użycie set()  na istniejącym już kluczu tabeli da ten sam efekt. Zwraca true lub wyrzuci wyjątek błedów gdy zapis nie będzie możliwy.

delete(string $key) - skasuje nazwę klucza. Zwraca true lub wyrzuci wyjątek błedów gdy zapis nie będzie możliwy.

flush() - kasuje zawartość bazy danych. Zwraca true lub wyrzuci wyjątek błedów gdy zapis nie będzie możliwy.

getKeys() - zwróci tablicę ze wszystkimi kluczami tabeli bazy danych.

Flintstone::unload(string $database) - wyładowanie bazy danych (zniszczy obiekt).

 


Przykład praktyczny - licznik odwiedzin w RazorCMS

Powiedzmy, że naszym zadaniem będzie stworzenie prostego licznika odsłon stron w oparciu o zabezpieczenie na unikalnym adresie IP oraz czasie od ostatnich odwiedzin.

$ip_goscia - czyli adres IP odwiedzającego otrzymamy z napisanej funkcji, której celem jest ustalenie adresu IP naszego gościa.

Dla celów zabawy na localhost, możemy zasymulować odwiedzających używając funkcji losowej generującej IP w sposób pseudolosowy:

 
$ips = Array('192.168.1.221', '120.843.592.86', '256.865.463.267', '100.865.463.266', '206.865.463.205', '256.865.463.234', '256.865.463.213', '256.865.463.243');
$ip_goscia = $ips[rand(1,count($ips)-1)];

Uruchamiamy naszą bazę danych w oparciu o Flinstone:

 
require RAZOR_BASE_PATH.'/vendor/autoload.php';
use Flintstone/Flintstone; 
			try { 
				// Set flat file database folder for files
					$counter_db = array('dir' => RAZOR_BASE_PATH.'/data/counter/');
 
				// Load the databases
					$token = Flintstone::load('token', $counter_db); // zapisuje token on-off uprawniajacy do zapisu do bazy
					$counter = Flintstone::load('counter', $counter_db); // przechowuje licznik odwiedzin
					$users_db = Flintstone::load('users_db', $counter_db); // przechowuje tablice z IP odwiedzajecego, daty, zegar, link podstrony
 
// tutaj będziemy bawić się z tabelą odwiedzin i danymi licznika
 
// Flush the token - zwalniamy token
		$token->flush();	
// end //				
	}
 
			}
 
catch (FlintstoneException $e) {
    echo 'An error occured: ' . $e->getMessage();
}					

W naszym przykładzie edukacyjnym naszego licznika odwiedzin, ważne jest, by nic nie zakłócało procesu zapisu do pliku. W tym celu został utworzony plik z tokenem, którego istnienie warunkuje proces przetwarzania danych i zapisu do plików.

 
// badamy token, czy czasem nie jest już w użyciu
// otrzymamy poleceniem get wartość aktualnego tokenu
$is_busy = $token->get('token');
 
// dla celów dydaktycznych możemy wyświetlać jego wartość
echo ' Token kontrolny ma wartość: ' . $token->get('token');
 
// badamy, czy nikt inny nie odwiedza strony, by móc zwiększyć licznik o 1 dla unikalnej wizyty co trzeba sprawdzić i zapisać do plików
// jeśli istnieje token i jego wartość to zero, to uruchamiamy zliczenie
	if(isset($is_busy) && $is_busy == 0)	{
 
		$token->set('token', 1);	// zakładamy blokadę na zmiany w plikach aż do zwolnienia
 
 
			// Retrieve keys
// sprawdzamy, czy nasz nowy token ma wartość 1 - czyli blokada działa
		$is_busy = $token->get('token');
 
		echo ' Token nowy jest ustawiony na: ' . $token->get('token');				
 
		// set script in use
 
// teraz zbadamy aktualną wartość liczby licznika odwiedzin z pliku			
			// Retrieve keys
		$is_counter = $counter->get('counter');
 
// możemy wyświetlić sobie uzyskaną starą wartość licznika
		echo '  Licznik miał starą wartość: ' . $counter->get('counter');

 

Linia tworząca plik z naszą bazą danych wygląda następująco:

 
$users_db->set($is_counter, array('ip' => $ip_goscia, 'data' => $data_zapisu_pliku, 'zegar' => $czas_teraz, 'link' => $url ));

Przykładowe dwa elementy naszej tabeli z danymi odwiedzających będą wyglądać tak - klucze 33 i 34, czyli kolejno przydzielone numery dla odwiedzających:

 
33=a:4:{s:2:"ip";s:15:"256.865.463.263";s:4:"data";s:19:"2017-11-20 20:48:05";s:5:"zegar";i:1511207285;s:4:"link";s:75:"http://localhost/counter/kolejne-elementy-tablicy-current-next-prev-end.htm";}
34=a:4:{s:2:"ip";s:15:"256.865.463.213";s:4:"data";s:19:"2017-11-20 20:49:16";s:5:"zegar";i:1511207356;s:4:"link";s:75:"http://localhost/counter/kolejne-elementy-tablicy-current-next-prev-end.htm";}

Badamy zawartość naszej tabeli:

 
$keys_db = $users_db->getKeys(); // zwraca wszystkie klucze z tablicy jako ich numery
// $keys_db - dostajemy klucze tablicy - tak jakby numery kolejnych wpisów do pliku, czyli 33, 34, itd., same numerki, często inne niż numer linii w pliku.

Na przykład gdy $i = $keys_db[5];  // pierwszym kluczem naszej tablicy jest np. klucz o numerze x=5 pomimo, ze może to być nawet linia y w pliku.

Teraz, mając dostęp do wszystkich kluczy w pliku naszej tekstowej "bazy danych:, możemy policzyć ile ich jest razem:

 
$tmpcount = count($keys_db); // ile mamy linii danych w pliku, czyli ilość wpisów zanotowanych w bazie = ilość dostępnych kluczy w pliku/tabelce

Jeśli teraz chcemy odczytać dane z konkretnego klucza zapisu, możemy to odczytać z pliku:

 
$linia_tabelki = $users_db->get($i); // tabela zawierająca linię z wybranego klucza, tu z klucza $i

W efekcie otrzymujemy tabelę zawierającą linię wpisu:

 
// array (4)
// ip => "100.865.463.264"
// data => "2017-11-20 17:36:36" 
// zegar => 1511195796
// link => "http://localhost/counter00/array-count-values.htm" 

Na początek deklarujemy dwie tablice na nasze dane, potrzebne do budowy licznika odwiedzin:

 
$guest_ip = array(); // przygotowanie tablicy na zapisane wszystkie adresy IP gości z bazy				
$actual_ip = array(); // przygotowanie tablicy na IP teraz odwiedzających

Do tabeli dla aktualnie oglądających naszą stronę, możemy wpisać uzyskany adres IP naszego gościa.

 
$actual_ip = $ip_goscia;

Wkrótce będzie można zadziałać na kluczach tablicy, by otrzymać pełną tabelę danych:

 
 
			// działamy na kluczach wykonując operacje na elementach tablicy
foreach ($keys_db as $usersdata) {
 
// tablica $user - pojedyncza linia z tabeli $users_db zawierająca adres IP, datę i czas, odwiedzany adres linku	
    $user = $users_db->get($usersdata);
echo '<br>';	
echo 'Użytkownik nr '.$usersdata.', jego ip to: ' . $user['ip'];
 
// tutaj dodajemy do tablicy kolejne adresy IP, najnowsze na sam początek tablicy
	array_unshift($guest_ip, $user['ip']);
echo '<br>';	
 
echo 'Użytkownik nr '.$usersdata.', - data wejścia na link ' . $user['data'];
echo '<br>';	
 
echo 'Użytkownik nr '.$usersdata.', - zegar systemowy ' . $user['zegar'];
echo '<br>';
 
echo 'Użytkownik nr '.$usersdata.', - otworzony link: ' . $user['link'];
echo '<br>';	
}	

Oczywiście, to przykład dydaktyczny drukujący na ekranie wszystko.

 
 
// Użytkownik nr 5, jego ip to: 120.843.592.86
// Użytkownik nr 5, - data wejścia na link 2017-11-20 17:14:45
// Użytkownik nr 5, - zegar systemowy 1511194485
// Użytkownik nr 5, - otworzony link: http://localhost/counter00/funkcje-tablic-w-php-lista.htm		

Efektem przejścia pętli po kluczach jest nazbieranie poleceniem array_unshift($guest_ip, $user['ip']);  adresów IP do jednej tabeli, w której możemy potem porównywać, czy nasz nowy odwiedzający, czy on już w niej istnieje, czy jeszcze nie. Jak nie, to za chwilę można zarejestrować jego bytność i wartość licznika zwiększyć o 1.

 
// jesli w tablicy users_db nie ma adresu ip, to licznik zwiększamy o jeden, a jeśli gość ma adres IP w bazie, to wartość licznika się nie zmieni
// porównamy dwie tablice, aktualnych adresów IP z tymi zapisanymi już w bazie
 
		if(!in_array($actual_ip, $guest_ip)) {
		$licznik += 1;	
// zapisujemy zmianę wartości licznika o jeden do pliku			
		$counter->set('counter', $licznik);	
		echo ' Licznik ma teraz NOWĄ wartość: ' . $counter->get('counter');
		} else {			
		echo ' Licznik ma teraz STARĄ wartość: ' . $counter->get('counter');	
		}
 

Oczywiście, nasz dydaktyczny licznik byłby licznikiem wyłącznie unikalnych adresów IP. Nie zliczałby kolejnych wizyt z tego samego komputera po określonym czasie, gdzie możemy założyć sobie określone widełki, które dany adres "odblokują" niejako do ponownego zliczenia jako nowe odwiedziny na stronie..

Za chwilę nasz licznik możemy w podobny sposób rozbudować o statystyki odwiedzin i atrakcyjności konkretnych linków na stronie, co możemy już sobie obejrzeć na spokojnie, na zapleczu admina naszego CMS'a.

Innym zastosowaniem jest gromadzenie adresów IP tych gości, którzy testują ataki na stronę, zapis kodów skryptów jakie próbują stosować - by potem móc skutecznie zablokować im możliwość wyświetlania strony.

A wszystko to dzięki operacjom i liczeniu elementów w tabelce, będącą Flinstone flat file database.