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

---------

Brak komentarzy:

Prześlij komentarz