Etykiety

linux (14) php (14) Laravel (9) mysql (9) Hardware (8) Windows (6) sieci (5) PowerShell (4) software (4) MariaDB (3) apache (3) hosting (3) html (3) Microsoft (2) bezpieczeństwo LAN (2) cygwin (2) Akcesoria (1) CMS (1) Laptop (1) Open Office (1) drupal 7 (1) gpg (1) jquery (1) sieci LAN (1) xml (1) zabezpieczenie sieci LAN (1)

piątek, 28 sierpnia 2015

Wytwórnia obiektów MySql w php

Wstęp


Jakiś czas temu pisałem o tym, jak w bezpieczny sposób korzystać z bazy danych MySql w skryptach php. Dziś pragnę podzielić się z Wami wiedzą dotyczącą tak zwanych wytwórni obiektów z danych MySql. Temat przeznaczony jest dla czytelników z dobrą znajomością języka php i serwera MySql, którzy pragną korzystać z dobrodziejstw obiektowego podejścia php do zagadnień bazodanowych.

Posłużę się w tym miejscu przykładem kartoteki kontrahentów, w której znajdują się zarówno osoby fizyczne, jak i przedsiębiorstwa oraz skryptem php, którego zadaniem jest wytwarzanie obiektów z danych obecnych w kartotece.

Aby rozpocząć należy utworzyć bazę danych MySql o nazwie kartoteka, a w niej tabelę o nazwie kontrahenci. O tym jak utworzyć bazę danych i prostą tabelę MySql pisałem w artykule PHP a bezpiecznie połączenie z bazą danych MySQL. Jest w nim również mowa o tworzeniu użytkownika MySql i nadawaniu uprawnień dostępowych.

Aby utworzyć naszą przykładową tabelę kontrahenci można posłużyć się poleceniem CREATE TABLE z wiersza poleceń MySQL w następujący sposób:

CREATE TABLE `kontrahenci` (
`id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`imie` varchar(15) DEFAULT NULL,
`nazwisko` varchar(20) DEFAULT NULL,
`adres` varchar(25) NOT NULL,
`nazwa` varchar(20) DEFAULT NULL,
`miasto` varchar(20) NOT NULL,
`kodPocztowy` char(5) NOT NULL,
`typ` enum('O','F') DEFAULT NULL,
`nip` char(13) DEFAULT NULL,
`pesel` char(11) DEFAULT NULL,
`regon` char(14) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

Tabelę można oczywiście utworzyć także korzystając z interfejsu phpMyAdmin, bądź innym dowolnym sposobem. Osobiście preferuję pracować z wierszem poleceń MySql.

Utworzona tabela MySql
Utworzona tabela MySql


Po utworzeniu tabeli warto do niej wrzucić kilka przykładowych wierszy, bowiem pusta tabela do niczego się nam nie przyda. Dla celów niniejszego artykułu tabela powinna zawierać wiersze zarówno dotyczące osób fizycznych jak i te dotyczące firm. Pole typ określa rodzaj zawartości danego wiersza danych ('O' = osoba fizyczna, 'F' = firma).

Dodajemy zatem kilka przykładowych wierszy danych do nowo utworzonej tabeli kontrahenci:

MariaDB [kartoteka]> insert into kontrahenci (`imie`,`nazwisko`,`adres`,`miasto`,`kodPocztowy`,`pesel`,`typ`) values('Jan','Kowalski','Plac Wolności 1','Warszawa','00001','01010101010','O');
Query OK, 1 row affected (0.02 sec)

MariaDB [kartoteka]> insert into kontrahenci (`imie`,`nazwisko`,`adres`,`miasto`,`kodPocztowy`,`pesel`,`typ`) values('Janusz','Kowal','Plac Wolności 2','Warszawa','00001','02020202020','O');
Query OK, 1 row affected (0.03 sec)

MariaDB [kartoteka]> insert into kontrahenci (`nazwa`,`adres`,`miasto`,`kodPocztowy`,`nip`,`regon`,`typ`) values('Jakaś Firma','Plac Wolności 3','Warszawa','00002','1231231231231','12121212121212','F');
Query OK, 1 row affected (0.00 sec)

MariaDB [kartoteka]> insert into kontrahenci (`nazwa`,`adres`,`miasto`,`kodPocztowy`,`nip`,`regon`,`typ`) values('Jakaś Inna Firma','Plac Wolności 4','Warszawa','00002','1241241241241','14141414141414','F');
Query OK, 1 row affected (0.02 sec)

Przykładowe dane można oczywiście wprowadzić dowolną metodą, nie koniecznie z wiersza poleceń MySql.

Wprowadzanie przykładowych danych do tabeli MySql
Wprowadzanie przykładowych danych do tabeli MySql z wiersza poleceń
 
Zawartość tabeli MySql po wprowadzeniu przykładowych danych
Zawartość tabeli po wprowadzeniu przykładowych danych

 

Wytwórnia obiektów MySQL w php


Ze strony MySql jest już wszystko gotowe. Przejdźmy teraz do skryptu php, który umożliwia tworzenie obiektów na podstawie danych zawartych w tabeli MySql. Od czego zacząć? Przede wszystkim będzie nam potrzebne połączenie z bazą MySql, oraz kilka prostych klas.

Plik db_ini.php zawiera wartości umożliwiające nawiązanie połączenia z serwerem MySql, takie jak nazwa hosta, nazwa użytkownika i hasło. Należy pamiętać, że nazwa użytkownika i hasło muszą być zgodne z rzeczywiście utworzonym użytkownikiem, a także o nadaniu użytkownikowi odpowiednich uprawnień do bazy danych i tabeli. O tym jak się to robi pisałem w artykule PHP a bezpiecznie połączenie z bazą danych MySQL. Oto zawartość pliku db_ini.php:

<?php
// Dostęp do bazy danych
$host = 'localhost';
$db = 'kartoteka';
$user = 'mike';
$password = 'kot-dachowiec';
?>

Skrypt php zawiera kilka klas, zdefiniowanych w bardzo prosty sposó i kilka linijek prostego kodu, tak aby skupić się na ogólnym jego działaniu i ewentualnym zastosowaniu wytwórni obiektów MySql. Na wszelkie pomysły związane z udoskonaleniami i rozbudową skryptu pozostawiam wolne pole czytelnikom.

Klasy OsobaFizyczna i Firma dziedziczą składowe i przysłaniają metody abstrakcyjnej klasy nadrzędnej Kontrahent. Uzupełniają one klasę Kontrahent ważnymi dla konkretnego celu składowymi i metodami, co oznacza, że klasy OsobaFizyczna i Firma są swojego rodzaju specjalizacjami nadrzędnej klasy Kontrahent. Firma przeważnie posiada nip i regon, podczas gdy dla identyfikacji osoby fizycznej ważny jest identyfikator PESEL. W takich przypadkach nie ma sensu tworzyć jednej rozbudowanej klasy ze wszystkimi możliwymi składowymi dla firm i osób prywatnych, ponieważ dawało by to miejsce pewnej nadmiarowości: powstałe w ten sposób obiekty zawierałyby puste i niewykorzystywane zmienne. Zarówno firma, jak i osoba fizyczna, posiadają pewne wspólne cechy, takie jak adres, kod pocztowy, miasto itp., ale jednocześnie różnią się dodatkowymi informacjami. Warto jest więc w tym przypadku utworzyć jedną abstrakcyjną klasę nadrzędną oraz 2 wyspecjalizowane klasy pochodne.

Klasy OsobaFizyczna i Firma umożliwiają tworzenie egzemplarzy odpowiadających im obiektów i zawierają podstawowe metody umożliwiające pozyskiwanie związanych z utworzonymi obiektami informacji. Konstruktor klasy Kontrahent dostępny jest tylko ze swoich klas pochodnych. Natomiast statyczna metoda getInstance() klasy Kontrahent dostępna jest publicznie. Dla zwiększenia bezpieczeństwa wszystkie składowe klas zdefiniowałem są jako prywatne.

Najistotniejsza dla niniejszego przykładu jest statyczna funkcja getInstance() klasy Kontrahent. Funkcja ta pobiera, za pomocą nawiązanego uprzednio połączenia z MySql z wykorzystaniem klasy PDO, wybrane wiersze danych z tabeli kontrahenci, a w zależności od tego, czy dany wiersz dotyczy osoby fizycznej czy firmy, funkcja tworzy nowy egzemplarz odpowiedniego obiektu. Tego typu rozwiązanie nazywamy wytwórnią obiektową.

Klasa KontrahentInfo zawiera metody umożliwiające wyświetlanie danych dotyczących obiektów klasy Kontrahent i jej podrzędnych klas.

Oto skrypt:

<?php

// Dostęp do bazy danych
include("db_ini.php");

abstract class Kontrahent {
    private $id;
    private $adres;
    private $kodPocztowy;
    private $miasto;

    protected function __construct($id, $adres, $kodPocztowy, $miasto) {
        $this->id = $id;
        $this->adres = $adres;
        $this->kodPocztowy = $kodPocztowy;
        $this->miasto = $miasto;
    }

    public static function getInstance($id, PDO $pdo) {
        $stm = $pdo->prepare("select * from kontrahenci where id=?");
        $stm->execute( array( $id ) );
        $row = $stm->fetch(PDO::FETCH_ASSOC);
      
        if (empty($row)) { return null; }
      
        if ($row['typ'] == "O") {
      
        // Osoba Fizyczna
        $obj = new OsobaFizyczna ($row['id'], $row['adres'], $row['kodPocztowy'], $row['miasto'], $row['imie'], $row['nazwisko'], $row['pesel']);
              
        } elseif ($row['typ'] == "F") {
      
        // Firma
        $obj = new Firma ($row['id'], $row['adres'], $row['kodPocztowy'], $row['miasto'], $row['nazwa'], $row['nip'], $row['regon']);
      
        } else return null;

        return $obj;  
    }
  
    protected function getId() {
        return "ID: {$this->id}";
    }
  
    protected function getInfo() {
        $base = "Adres: {$this->adres}\n".
                "Kod pocztowy: {$this->kodPocztowy}\n".
                "Miasto: {$this->miasto}";
              
        return $base;
    }
}

class OsobaFizyczna extends Kontrahent {
    private $imie;
    private $nazwisko;
    private $pesel;
  
    public function __construct ($id, $adres, $kodPocztowy, $miasto, $imie, $nazwisko, $pesel) {
        parent::__construct($id, $adres, $kodPocztowy, $miasto);
        $this->imie = $imie;
        $this->nazwisko = $nazwisko;
        $this->pesel = $pesel;
    }
  
    public function getInfo() {
        $base = parent::getId()."\n";
        $base.= "Imie: {$this->imie}\n";
        $base.= "Nazwisko: {$this->nazwisko}\n";
        $base.= parent::getInfo()."\n";
        $base.= "PESEL: {$this->pesel}";
        return $base;
    }
}

class Firma extends Kontrahent {
    private $nazwa;
    private $nip;
    private $regon;
  
    public function __construct($id, $adres, $kodPocztowy, $miasto, $nazwa, $nip, $regon) {
        parent::__construct($id, $adres, $kodPocztowy, $miasto);
        $this->nazwa = $nazwa;
        $this->nip = $nip;
        $this->regon = $regon;
    }
  
    public function getInfo() {
        $base = parent::getId()."\n";
        $base.= "Nazwa: {$this->nazwa}\n";
        $base.= parent::getInfo()."\n";
        $base.= "NIP: {$this->nip}\n";
        $base.= "REGON: {$this->regon}";
        return $base;
    }
  
}

class KontrahenciInfo {
        public function showInfo(Kontrahent $kontrahent) {
            print "<pre>{$kontrahent->getInfo()}</pre><br>---------<br>";
        }
}

try {
    // Nawiązanie połączenia z bazą danych MySQL
    $conn = new PDO("mysql:host={$host};dbname={$db}", $user, $password,array(PDO::MYSQL_ATTR_INIT_COMMAND=>"SET NAMES utf8",PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
     
    for ($id=1;$id<=4;$id++) {
        $kontrahent = Kontrahent::getInstance($id,$conn);
        $info = new KontrahenciInfo;
        $info->showInfo($kontrahent);
    }
          
}    catch (PDOException $e) {
        // Obsługa wyjątków
        print "Błąd PDO: " . $e->getMessage() . "<br/>";
        die();
}

?>

Jak działa ten skrypt? Na początku skryptu występują definicje niezbędnych do jego działania klas. W następnym etapie nawiązujemy połączenie z bazą danych korzystając z popularnej bazodanowej klasy PDO. Dla celów niniejszego artykułu skrypt wyszukuje konkretne wiersze z tabeli, zdefiniowane pętlą for-next. Po nawiązaniu połączenia z bazą danych wywołujemy – w pętli for-next - zdefiniowaną w klasie Kontrahent statyczną metodę Kontrahent::getInstance(), do której przekazujemy obiekt PDO połączenia z bazą oraz identyfikator wiersza tabeli MySql kontrahenci i która w ten sposób odczytuje wybrane dane z tabeli i tworzy na ich podstawie zróżnicowane obiekty, odpowiednio klas OsobaFizyczna bądź Firma, w zależności od wartości pola typ w napotkanym wierszu MySql. W tej samej pętli tworzony jest także obiekt klasy KontrahenciInfo, a za pomocą metody KontrahenciInfo::ShowInfo() wyświetlane są informacje o każdym utworzonym obiekcie z podrzędnej hierarchii klasy Kontrahent. Nawiązanie połączenia MySql, odczyt wierszy danych z tabeli, tworzenie obiektów z odczytanych danych, a także korzystanie z metody wyświetlającej informacje o utworzonych obiektach, to czynności opatrzone w skrypcie w podstawowy mechanizm przechwytywania wyjątków try-catch.

A oto efekt działania skryptu:

ID: 1
Imie: Jan
Nazwisko: Kowalski
Adres: Plac Wolności 1
Kod pocztowy: 00001
Miasto: Warszawa
PESEL: 01010101010

---------
ID: 2
Imie: Janusz
Nazwisko: Kowal
Adres: Plac Wolności 2
Kod pocztowy: 00001
Miasto: Warszawa
PESEL: 02020202020

---------
ID: 3
Nazwa: Jakaś Firma
Adres: Plac Wolności 3
Kod pocztowy: 00002
Miasto: Warszawa
NIP: 1231231231231
REGON: 12121212121212

---------
ID: 4
Nazwa: Jakaś Inna Firma
Adres: Plac Wolności 4
Kod pocztowy: 00002
Miasto: Warszawa
NIP: 1241241241241
REGON: 14141414141414

---------

poniedziałek, 24 sierpnia 2015

PHP a bezpiecznie połączenie z bazą danych MySQL

Wstęp


W sieci web można dziś łatwo znaleźć obfitą ilość instruktaży dotyczących metod łączenia się z bazą danych MySQL z poziomu przeglądarki internetowej za pomocą języka php. Większość z nich to jednak metody bardzo ogólne i podstawowe, generalnie bazujące na oprogramowaniu strukturalnym, bez odpowiedniego podejścia do kwestii bezpieczeństwa kodu. Tego typu tutoriale są często źródłem złych nawyków programistycznych, które stają się barierą w rozwoju dalszych umiejętności. Ponieważ kod przedstawiony w danym instruktarzu działa, większość początkujących programistów jest przekonana o jego zupełniej prawidłowości i pełnej efektywności. Wiele z przykładów przedstawianych w popularnych instruktażach związanych z tematem, to metody, które w środowisku produkcyjnym raczej by się długoterminowo nie sprawdziły i na pewno w końcu by zawiodły. W niniejszym artykule postaram się przedstawić temat korzystania z bazy danych MySQL za pomocą języka php z perspektywy bezpieczeństwa i efektywności kodu, skupiając się na metodzie OOP (programowanie obiektowe) z zastosowaniem klasy PDO. W celu ułatwienia czytelnikom zrozumienia omawianej tutaj tematyki, przytoczę konkretne i sprawdzone przeze mnie przykłady. Artykuł jest skierowany do czytelników z dobrą znajomością php i przynajmniej podstawową wiedzą dotyczącą MySQL oraz programowania obiektowego.

Klasa PDO


Klasa PDO oferuje programistom php szeroki wachlarz możliwości jeśli chodzi o bazy danych, stanowiąc swego rodzaju abstrakcyjną powłokę dostępu do danych. PDO zapewnia dość wysoki poziom bezpieczeństwa przy minimalnej ilości kodu, a jednocześnie skrypty wykonywane są bardzo szybko. Stosowanie odpowiednich sterowników PDO umożliwia współpracę klasy z rozmaitymi systemami baz danych. Na dzień dzisiejszy wspierane są następujące rozwiązania bazodanowe:

IBM DB2
ODBC v3 (IBM DB2, unixODBC and win32 ODBC)
PostgreSQL
Cubrid
FreeTDS / Microsoft SQL Server / Sybase
Firebird
SQLite 3 and SQLite 2
Microsoft SQL Server / SQL Azure
4D IBM Informix Dynamic Server
MySQL 3.x/4.x/5.x
Oracle Call Interface

PHP PDO
PHP PDO


Niezależnie od stosowanego sterownika bazy danych, w każdym przypadku odczyt i manipulacja danymi odbywają się za pomocą identycznych funkcji tej klasy, co pozwala na szybkie opanowanie dostępnych w klasie narzędzi i stosowanie ich w pracy z różnymi rodzajami baz danych.

PHP a bezpieczne połączenie z bazą danych MySQL


Za pomocą klasy PDO można korzystać z dobrodziejstw MySQL z poziomu przeglądarki internetowej w prosty i bezpieczny sposób. Postaram się wyjaśnić jak to zrobić na podstawie prostego przykładu kartoteki kontrahentów. Aby rozpocząć, należy w dowolny sposób utworzyć bazę danych o nazwie kartoteka oraz prostą tabelę o nazwie kontrahenci. Dla jasności przykładu posłużę się w tym celu klientem MySQL MariaDB w środowisku Scientific Linux 7. Bazę i tabelę można oczywiście utworzyć w dowolny sposób, np. korzystając z interfejsu phpMyAdmin, bądź podobnego oprogramowania.
MariaDB [(none)]> create database kartoteka;
Query OK, 1 row affected (0.00 sec)

MariaDB [(none)]> use kartoteka;
Database changed

MariaDB [kartoteka]> create table kontrahenci (
-> id smallint unsigned not null auto_increment primary key,
-> imie varchar(15) not null,
-> nazwisko varchar(20) not null,
-> adres varchar(25) not null ) engine=InnoDB charset utf8;
Query OK, 0 rows affected (0.06 sec)
Po utworzeniu tabeli o nazwie kontrahenci należy utworzyć użytkownika o przykładowej nazwie mike i nadać mu uprawnienia niezbędne do odczytu i zapisu danych:
MariaDB [kartoteka]> grant usage on *.* to mike@localhost identified by 'kot-dachowiec';
Query OK, 0 rows affected (0.01 sec)

MariaDB [kartoteka]> grant select, insert, update on kartoteka.kontrahenci to mike@localhost;
Query OK, 0 rows affected (0.00 sec)
Warto sprawdzić, czy uprawnienia dla użytkownika mike są odpowiednio ustawione:
MariaDB [kartoteka]> show grants for mike@localhost;
+-------------------------------------------------------------------------------------------------------------+
| Grants for mike@localhost |
+-------------------------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'mike'@'localhost' IDENTIFIED BY PASSWORD '*391D5A941E13141F78608619467FC09552677273' |
| GRANT SELECT, INSERT, UPDATE ON `kartoteka`.`kontrahenci` TO 'mike'@'localhost' |
+-------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)
Teraz można przystąpić do sporządzenia formularza html, którego kod, dla prostoty i jasności przykładu, wygląda następująco:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
  "http://www.w3.org/TR/html4/strict.dtd">
<html>
<h3>Dodawanie kontrahenta do kartoteki</h3>
<form method="post" action="dodaj.php">
Imię: <input type="text" name="imie" size="15" maxlength="15"> <br /><br />
Nazwisko: <input type="text" name="nazwisko" size="20" maxlength="20"> <br /><br />
Adres: <input type="text" name="adres" size="25" maxlength="25"> <br /><br />
<input type="submit" value="Dodaj">
</form>
</html>
Powyższy formularz ma za zadanie przesyłanie wprowadzonych do niego danych metodą POST do skryptu dodaj.php. Kod html formularza zawiera tylko te elementy, które są niezbędne dla zrozumienia przedstawianego tutaj przykładu, nie zawiera natomiast elementów takich jak style css, bądź innych elementów poprawiających wygląd czy funkcjonalność.

W pliku o przykładowej nazwie db_ini.php należy umieścić zmienne umożliwiające dostęp do bazy danych:
<?php
// Dostęp do bazy danych
$host = 'localhost';
$db = 'kartoteka';
$user = 'mike';
$password = 'kot-dachowiec';
?>

A oto kod skryptu dodaj.php:
<?php
// Dostęp do bazy danych
include('db_ini.php');
try
{
// Nawiązanie połączenia z bazą danych MySQL
$conn = new PDO("mysql:host={$host};dbname={$db}", $user, $password,
  array(PDO::MYSQL_ATTR_INIT_COMMAND=>"SET NAMES utf8",
  PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));

// Dodawanie nowego wiersza danych
$dodaj = $conn->prepare("insert into kontrahenci (imie, nazwisko, adres) values(:imie,:nazwisko,:adres)");
$dodaj->execute(array(':imie'=>$_POST['imie'],':nazwisko'=>$_POST['nazwisko'],':adres'=>$_POST['adres']));

// Zakończenie połączenia
$conn = null;
}

catch (PDOException $e) {
// Obsługa wyjątków
print "Błąd: " . $e->getMessage() . "<br/>";
die();
}
?>
Skrypt dodaj.php ma za zadanie nie tylko wprowadzenie przesłanych za pomocą formularza html danych do tabeli MySQL, lecz także zapewnienie podstawowego poziomu obsługi wyjątków, którą można w prosty sposób dopasować do warunków typowego środowiska produkcyjnego. Skrypt powoduje dodanie nowego wiersza (rekordu) do tabeli kontrahentów. W przypadku wystąpienia wyjątku zostają wykonane polecenia z sekcji catch, którą można dowolnie rozbudować na miarę własnych potrzeb. Należy zwrócić uwagę na specyficzne atrybuty połączenia $conn. Po pierwsze, ponieważ tabela kontrahentów obsługuje system kodowania znaków utf-8, połączenie zostaje utworzone właśnie z obsługą tego standardu kodowania - MYSQL_ATTR_INIT_COMMAND=>"SET NAMES utf8" - co umożliwia prawidłową obsługę polskich czcionek. Po drugie, ponieważ skrypt dodaj.php ma zapewnić podstawową obsługę wyjątków, połączenie zostaje nawiązane z atrybutem PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION. W przypadku ustawień obsługi wyjątków jak w przykładzie powyższego skryptu dodaj.php, pojawienie się wyjątku nie tylko daje o sobie znać w zdefiniowany przez programistę sposób, czyli w postaci wyświetlonej informacji, ale także rozpoczęta przez skrypt transakcja dodawania wpisu zostaje automatycznie wycofana, co zapewnia dobre zabezpieczenie przed skutkami ewentualnych błędów. Przykładowo, jeśli programista przeliteruje w skrypcie nazwę kolumny 'imie' tabeli 'kontrahenci', to transakcja zostanie wycofana, a jednocześnie wyświetli się następujący komunikat:
Błąd: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'imse' in 'field list'
Przechwytywanie wyjątków daje duże możliwości programistyczne, przy jednoczesnej minimalizacji kodu.

Co bardzo istotne dla każdego świadomego programisty php, wszelkie zapytania przekazywane do MySQL poprzez klasę PDO nie stanowią jakiejkolwiek luki bezpieczeństwa. W celu uzyskania maksymalnego poziomu bezpieczeństwa, PDO umożliwia stosowanie sparametryzowanych zapytań poprzez ich przygotowanie metodą prepare oraz powiązanie z wartościami zmiennych przekazanych w tablicy poprzez metodę execute (jak w niniejszym przykładzie). W celu zachowania wysokiego poziomu bezpieczeństwa zapytań PDO można równie dobrze korzystać z metod ::bindParam lub ::bindValue. Nie ma konieczności korzystania z dodatkowych narzędzi typu mysql_real_escape_string() w przykładowym celu zapobiegania atakom sql injection. Klasa PDO eliminuje potrzebę stosowania typowo proceduralnych zabezpieczeń przed różnego rodzaju atakami związanymi z dostępem do baz danych.

W celu uzupełnienia przykładu bezpiecznego zapisu danych, udostępniam przykład odczytu danych z bazy z wykorzystaniem klasy PDO:
<?php
// Dostęp do bazy danych
include('db_ini.php');
try
{
// Nawiązanie połączenia z bazą danych MySQL
$conn = new PDO("mysql:host={$host};dbname={$db}", $user, $password,
  array(PDO::MYSQL_ATTR_INIT_COMMAND=>"SET NAMES utf8",
  PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));

// Odczyt i wyświetlanie wierszy danych
$dane=$conn->prepare("select id, imie, nazwisko, adres from kontrahenci");
$dane->execute();
$result = $dane->fetchAll(PDO::FETCH_ASSOC);

// Zakończenie połączenia
$conn = null;
}

catch (PDOException $e) {
// Obsługa wyjątków
print "Błąd: " . $e->getMessage() . "<br/>";
die();
}

// Wyświetlanie wyników
foreach ($result as $kontrahent)
 print "{$kontrahent['id']} - {$kontrahent['imie']} {$kontrahent['nazwisko']}
 {$kontrahent['adres']} <br /&gt";
?>

środa, 19 sierpnia 2015

Amanda Backup: Amrecover - przywracanie zarchiwizowanych danych

Wstęp


Jakiś czas temu opracowałem bardzo ogólny artykuł dedykowany systemowi archiwizacji danych Amanda, w którym przedstawiłem nie tylko ogólny zarys tego znakomitego i darmowego oprogramowania, lecz także przytoczyłem nieco udokumentowany przykład pewnego sprawdzonego wariantu podstawowej konfiguracji systemu.

Dobrze zaplanowana archiwizacja danych jest bardzo ważna, lecz kluczowe znaczenie ma również umiejętność przywracania zarchiwizowanych po stronie serwera plików. Ponieważ kwestia przywracania plików to, w przypadku systemu Amanda, bardzo obszerny temat, w niniejszym artykule mam zamiar omówić bardzo prosty przykład przywracania zarchiwizowanych danych do archiwizowanego klienta z systemem operacyjnym Linux, za pomocą oprogramowania Amrecover.

Należy pamiętać o tym, iż z oprogramowania Amrecover można korzystać tylko i wyłacznie jako użytkownik root na archiwizowanej maszynie.

Przykładowy scenariusz Amrecover


Serwer archiwizacji - z zainstalowanym i prawidłowo skonfigurowanym, dla celów niniejszego przykładu, oprogramowaniem amanda-server - o nazwie hosta amanda i adresie ip 192.168.0.2. Archiwizowany klient Linux o nazwie hosta bob3 i adresie ip 192.168.0.3, z zainstalowanym i prawidłowo skonfigurowanym, dla celów niniejszego przykładu, oprogramowaniem amanda-client.

Ustawienia wstępne


Aby oprogramowanie Amrecover działało prawidłowo kilka ważnych warunków musi zostać spełnionych.

1. Po stronie serwera archiwizacji danych należy sporządzić odpowiednią zawartość pliku /var/lib/amanda/.amandahosts. Dla niniejszego przykładu plik .amandahosts powinien koniecznie zawierać następującą linię, uprawniającą użytkownika root na maszynie adresie ip 192.168.0.3 do dostępu do systemu indeksacji zarchiwizowanych danych oraz do zarchiwizowanej zawartości:
192.168.0.3 root amindexd amidxtaped

2. Po stronie archiwizowanego klienta plik /etc/amanda/amanda-client.conf powinien zawierać następujące linie:
conf  "TestConfig1" # nazwa konfiguracji zgodnie z konfiguracją serwera
index_server "192.168.0.2" # serwer indeksacji danych
tape_server  "192.168.0.2" # serwer archiwizacji
tapedev "" # urządzenie taśmowe

Amrecover w akcji


Jako użytkownik root, po stronie archiwizowanej maszyny Linux zaprzęgamy do pracy Amrecover.
[root@bob3 tmp_test]# amrecover
AMRECOVER Version 3.3.3. Contacting server on amanda.intranet.bla.pl ...
220 amanda AMANDA index server (3.3.3) ready.
Setting restore date to today (2015-08-19)
200 Working date set to 2015-08-19.
200 Config set to TestConfig1.
501 Host bob3 is not in your disklist.
Use the sethost command to choose a host to recover
Po uruchomieniu oprogramowania Amrecover warto się upewnić, że na serwerze archiwizacji znajdują się zarchiwizowane dane z hosta bob3. Sprawdzamy więc listę zarchiwizowanych maszyn za pomocą polecenia listhost:
amrecover> listhost
200- List hosts for config TestConfig1
201- 192.168.0.3
200  List hosts for config TestConfig1
Ponieważ nasza maszyna znajduje się na liście, możemy śmiało kontynuować.
Za pomocą polecenia sethost wybieramy nazwę hosta, którego zarchiwizowane dane mamy zamiar przywrócić:
amrecover> sethost 192.168.0.3
200 Dump host set to 192.168.0.3.
Została prawidłowo wybrana nazwa hosta, którego dane nas interesują. Teraz, korzystając z polecenia listdisk uzyskujemy listę zarchiwizowanych na serwerze katalogów wybranego hosta bob3:
amrecover> listdisk
200- List of disk for host 192.168.0.3
201- /home/bob/dom/multimedia/Muzyka
200 List of disk for host 192.168.0.3
Ponieważ szybko znajdujemy katalog który nas interesuje, śmiało wybieramy go za pomocą polecenia setdisk:
mrecover> setdisk /home/bob/dom/multimedia/Muzyka
200 Disk set to /home/bob/dom/multimedia/Muzyka.
Po wybraniu interesującego nas dysku wyświetlamy jego zawartość:
amrecover> ls
2015-08-19-14-26-04 "gunnar Qmusic/"
2015-08-19-14-26-04 DreamcatcherQ/
2015-08-19-14-26-04 .
Ponieważ mamy zamiar przywrócić wyłącznie katalog DreamcatcherQ, wybieramy go za pomocą polecenia add:
amrecover> add Dreamcatcher
Added dir /Dreamcatcher/ at date 2015-08-19-14-26-04

Należy pamiętać o tym, że wybrane pliki lub katalogi są przywracane do lokalizacji ścieżki, w której obecnie się znajdujemy!

Przystępujemy do przywracania zarchiwizowanych plików za pomocą polecenia extract.
amrecover> extract

Extracting files using tape drive changer on host amanda.intranet.bla.pl.
The following tapes are needed: TestConfig1-1

Extracting files using tape drive changer on host amanda.intranet.bla.pl.
Load tape TestConfig1-1 now
Continue [?/Y/n/s/d]? y
Restoring files into directory /root/tmp_test
All existing files in /root/tmp_test can be deleted
Continue [?/Y/n]? y

./DreamcatcherQ/
./DreamcatcherQ/ab.ini
./DreamcatcherQ/dreamcatcherQ_wav.wav

amrecover>
Dane zostały skutecznie przywrócone, co w przykładowym przypadku umożliwi dalsze słuchanie ulubionej muzyki!

Amrecover oferuje cały szereg przydatnych w odzyskiwaniu danych poleceń. Aby wyświetlić całą listę poleceń, należy z poziomu wiersza poleceń amrecover wydać polecenie help.

W przypadku uwag bądź pytań proszę o komentarze. Służę pomocą!