webForumDet fria alternativet

Koppla till databas med PDO-klass

13 svar · 883 visningar · startad av learn

learnMedlem sedan jan. 20051 130 inlägg
#1

Jag försöker få det här med klasser att fungera.

Jag har skapat en enkel klass med metoder för att koppla upp sig till databasen med hjälp av PDO och sedan en rad andra klasser för att hämta data ur databasen.

Så här ser databas-kopplings-klassen ut:

class db_handler
{
	private $host;
	private $username;
	private $password;
	private $database;
	protected $db;

	// Constructor
	function __construct()
	{
		$host = 'localhost';
		$username = 'root';
		$password = '';
		$database = 'test';

	}
	
	// Connect to db
	protected function connect()
	{
		try
		{
			$db = new PDO('mysql:host='.$host.';dbname='.$database, $username,$password);
			$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
			$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, 1); 
			
			echo 'Connected to DB';
		}
		
		catch(PDOException $e)
		{
			echo $e->getMessage();
		}
	}
	
	// Disconnect from db
	protected function disconnect()
	{
		unset($db);
		echo 'Disconnected from DB';
	}

}

Och så här kan en hämta-data-klass se ut (vilken ärver kopplings-klassen):

class test extends db_handler
{
	private $stmt;
	private $arguments;
	private $result;
	
	public function get_users($arguments)
	{
		try
		{
			$this->connect();
			
			$stmt = $db->prepare('
			SELECT user_id, username, password, email
			FROM user
			WHERE username = ? AND password = ?
			');
		
			$stmt->execute($arguments); 
			$result = $stmt->fetchAll();
			
			$this->disconnect();
			return $result;
			
		}
		catch(PDOException $e)
		{
			echo $e->getMessage();
		}
	}
}

Men detta funkar inte alls! Jag som tycker jag vart så duktig, nybörjare och allt. :)

Får detta error:

Fatal error: Call to a member function prepare() on a non-object in C:\...\class_lib\test.php on line 28

$db tycks inte vilja bli ett objekt... Snälla hjälp mig på traven! :bire

pulseMedlem sedan nov. 200373 inlägg
#2

Det är nog för att du glömt använda "this"
I db_handler > connect()
ska det vara $this->db = new Pdo...

I test > get_users()
ska det också vara $this->db->prepare

Förövrigt borde du inte låta test ärva från db_handler utan skilj på databasanslutningen och klasserna som utför själva frågorna:

class test {

private $db;

	function __construct(){
		$this->db = db_handler->getConnection(); //få ett PDO objekt av db_handler
	}

	function get_users($arguments){
		//låt db_handler sköta connect, disconnect osv (dölj/kapsla in all kod som inte är relevant för den här klassen. Sträva efter att varje klass har en uppgift som den är bra på. Dublicera inte kod i onödan.)
		$stmt = $this->db->prepare("select * from...")
	}
pulseMedlem sedan nov. 200373 inlägg
#3

Här är en db klass som jag använt i ett eget projekt. För att få en anslutning skriver du Db::getInstance();

Det här klassen använder singletonmönstret som garanterar att det bara kommer existera en instans av klassen. I din db_handler så räcker det om koden innuti connect körs en gång men du kommer köra den varje gång du gör en ny sql-fråga vilket är onödigt. Singleton tvingar dig att skapa ETT db objekt och sen återanvända det under hela requesten. Det sparar både resurser och minskar mängden kod som behöver skrivas.

<?php

/**
 * Class that handles the connection to the database
 *
 * @category Database
 * @author ...
 */

abstract class Db {
	
	private static $dsn = "...";
	private static $user = "...";
	private static $password = "...";
	private static $instance = null;
	
	private function __construct() {
	}
	
	/**
	 * Instanciate new Connection to Database via PDO
	 * @return void
	 */
	private static function connect() {
		try {
			self::$instance = new PDO(self::$dsn, self::$user, self::$password);
			self::$instance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
			self::$instance->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
			self::$instance->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
			self::$instance->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
			$sql = "SET SESSION sql_mode = 'STRICT_ALL_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE'";
			self::$instance->query($sql);
		} catch(PDOException $e) {
			echo 'Database connection failed<br>';
			echo $e->getMessage();
			exit();
		} catch(Exception $e) {
			echo $e->getMessage();
			exit();
		}
	}
	
        /** @return PDO */
	public static function getInstance() {
		if(! isset(self::$instance)) {
			self::connect();
		}
		return self::$instance;
	}
	
	public static function disconnect(){
		self::$instance = null;
	}

}

?>
learnMedlem sedan jan. 20051 130 inlägg
#4

Ok, tack för ditt svar, jag ska genast prova!

Några snabba frågor bara:

1. Varför skriva "abstract", vad händer då?
2. Samma med "static"...
3. Kan jag fortfarande köra med mina statements?
4. Har du någon bra länk till info kring det du kallar "singletonmönster", gärna på svenska?
5. När disconnectas jag från databasen? Ska jag anropa det själv på något sätt? Själv trodde jag att PHP gjorde detta automatiskt när sidan laddats klart.

Ja, det var vad jag undrade så här på en gång.

Tack!

learnMedlem sedan jan. 20051 130 inlägg
#5

En fråga till:

6. När väljer jag databas??

Ge mig gärna ett enkelt exempel på en klass som använder getInstance. Jag känner mig lite lost nu!

learnMedlem sedan jan. 20051 130 inlägg
#6

En fråga till:

6. När och hur väljer jag databas??

Ge mig gärna ett enkelt exempel på en klass som använder getInstance. Jag känner mig lite lost nu!

learnMedlem sedan jan. 20051 130 inlägg
#7

Följande tycks fungera, men jag förstår inte riktigt vad jag gör (se mina frågor ovan).

Ser detta bra ut eller har jag missförstått något:

abstract class db_handler
{
	private static $dsn = 'mysql:host=localhost;dbname=test';
	private static $username = 'root';
	private static $password = '';
	private static $instance = null;
	
	private function __construct()
	{
	}
	
	// Connect to db
	private static function connect()
	{
		try
		{
			self::$instance = new PDO(self::$dsn, self::$username, self::$password);
			self::$instance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
			self::$instance->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
			self::$instance->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
			self::$instance->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
			$sql = "SET SESSION sql_mode = 'STRICT_ALL_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE'";
			self::$instance->query($sql);
		} 
		
		catch(PDOException $e)
		{
			echo 'Database connection failed<br />';
			echo $e->getMessage();
			exit();
		} 
		
		catch(Exception $e)
		{
			echo $e->getMessage();
			exit();
		}
	}
	
    // Establish connection to db
	public static function get_instance()
	{
		if (! isset(self::$instance))
		{
			self::connect();
		}
		
		return self::$instance;
	}
	
	// Disconnect from db
	public static function disconnect()
	{
		self::$instance = null;
	}
}

Hämta data:

class test
{
	private $db;
	private $stmt;
	private $arguments;
	private $result;
	
	// Get users depending on the array $arguments
	// $arguments: username, password
	public function get_users($arguments)
	{
		try
		{	
			$db = db_handler::get_instance();
			
			$stmt = $db->prepare('
			SELECT user_id, username, password, email
			FROM user
			WHERE username = ? AND password = ?
			');
		
			$stmt->execute($arguments); 
			$result = $stmt->fetchAll();
						
			db_handler::disconnect();
			
			return $result;
		}
		
		catch(PDOException $e)
		{
			echo $e->getMessage();
		}
	}
}
tantenMedlem sedan nov. 20051 596 inlägg
#8

Nu känns det som att en riktig nybörjare svarar på nybörjarfrågor... men jag känner så väl igen mig i dina "undranden". :)
Jag tyckte det var så svårt att hitta något om klasser på nätet som var till för php5. Och det är väl så att dokumentationen inte alls är lika omfattande som för php4 och då är skillnaderna milsvida mellan de två.

Så det tog ett bra tag innan jag förstod detta med att bygga hela frameworks innan jag kom fram till filerna som skulle visa något... men under tiden kan man dumpa ut resultat och se att det fungerar...

Du skrev i ditt förra inlägg att du planerade ett större projekt... Kanske kan http://www.pradosoft.com/features/ få dig en bit på vägen. När jag tragglar med deras demos så har många poletter trillat ner även om det är en bra bit kvar. PDO-kopplingar finns med även om de mest använder sig av sqllite, som verkar vara väldigt smidigt.
Jag är som sagt nybörjare också... och kanske finns det någon annan som säger att PRADO är skräp. Men mig har det fått att tänka till på ett annat sätt.
Dina frågor om abstract osv. finns med där. Deras filer är väldigt bra kommenterade och ska man göra ett litet större projekt så ... du kan ju testa... :)

colioneMedlem sedan juni 20014 421 inlägg
#9

en abstrakt klass är en klass som det är tänkt att andra klasser ska ärva från och går därför inte att instantiera. däremot kan subklassen nyttja alla publika och protectade egenskaper ich metoder, det gäller även konstruktorn och magic methods. detta kan man dock påverka med keywordet final.

ett exempel på när en abstrakt klass kan användas är t.ex en klass som jar en output-metod sim skriver ut alla satta egenskaper och dess värden. nu kan du ha kkasser som hamtar info och bearbetar den och du slipper sha med samma output-funktion inskriven i dessa klasser.

just keywordsen tycker jag finns bra beskrivna i manualen.

jag skulle dock vilja varna för att använda singketons samt en överdriven mängd statics då det lät drar en tillbaka till funktionsbaserad prigramering, om än inkapslat i en klass.

i övrigt tycker jag att singletons är dumt att använda annars med. hur ofta kan du vara stensäker på att du inte behöver en annan instans av den klassen? ponera att din applikation blir beroende av två skilda databaser, vad gör du då? skapar en likadan singleton med annat namn och andra uppgifter? stanger din första instans och skapar en annan? om du senare behover anvanda info från db2 i db1 skapar du om den forsta resursen då? det är bättre att ha koll på sina objekt an att arbeta med singletons, tycker jag. kanske genom att jobba medett registry.

ni får ursäkta felstavningar och osammanhängande text, en p1 är väldigt icke-optimalt att wf-posta från.

pulseMedlem sedan nov. 200373 inlägg
#10

Jag tycker din kod ser bra ut. Du kan ta bort db_handler::disconnect(); för det görs automatiskt. Det finns en standard på hur man ska skriva kommentarer som heter phpdoc. Du kan se den här:
http://manual.phpdoc.org/HTMLSmartyConverter/HandS/phpDocumentor/tutorial_tags.pkg.html
Kanske kan du lägga in all denna kod i samma metod

$db = db_handler::get_instance();
$stmt = $db->prepare...
$stmt->execute...
$result = $stmt...
return $result

Det vore snyggare att bara skriva:

return Db::fetchAll($sql, $arguments);

Skaffa en editor i php som hjälper dig med OO. Den bästa jag vet är zend Studio som du kan ladda ner här:
http://www.zend.com/en/products/studio/downloads

Abstract, static, interface, polymorphism, inkapsling mm är olika metodiker inom OO som du kommer stöta på sen när du suttit med det ett tag. Det är svårt att förklara exakt när och varför man ska använda allihop men lite kortfattat så kan man säga såhär:

Static
Det finns två olika typer av metoder på en klass: static och instans -metoder. För att komma åt instansmetoderna måste du först skapa ett objekt (en instans) av den klassen innan du kan använda dem. På static så skapas inget objekt utan det går att komma åt dem genom att skriva <klass>::<metod>

//det här är en instansmetod
$db = new Db();
$connection = $db->connect();
//det här är en statisk metod (ingen instans skapas)
$connection = Db::connect();

Man använder oftast statiska metoder som olika generiska "util-metoder" tex mattematiska formler och liknande. Math::abs($num); Math::pow($num);...
Se det som ett sätt att gruppera funktioner med liknande uppgift på ett och samma ställe. I ditt projekt kan du tex lägga in alla metoder som modifierar arrayer på något sätt som statiska metoder i en klass och kalla den ArrayUtil eller liknande. Sen kan du komma åt dem med ArrayUtil::sort($array), ArrayUtil::reverse($array);
Eftersom inget objekt skapas går det inte att använda $this i en statisk metod. Istället använder man self:: för att komma åt andra statiska metoder i den klassen. En statisk metod kan inte komma åt instansmetoder i samma klass (instansmetoder kräver ett objekt).

Abstrakt
En abtrakt klass fungerar som en slags mall eller ett kontrakt. Du kan definera metoder i den men du överlåter implementationen av metoderna till alla klasser som ska ärva från den.

abstract class Animal{
	abstract function speak(); //det här är en abstrakt metod, den är bara definerad men saknas implementation

	function eat(){ 
		echo "eating"; //en abstrakt klass kan innehålla vanliga metoder också. De ärvs som vanligt.
	}
}

class Dog extends Animal{
	//här kommer php gnälla för att du inte har implementerar speak();
}

class Cat extends Animal{
	function speak(){
		echo 'mjaau'; //här har vi implementerar speak korrekt
	}
}

Abstrakta metoder garanterar att alla klasser som ärver från den abstrakta klassen har implementerat metoden. Detta är ett krav för att Polymorphism ska fungera.
Att göra min db klass abtrakt var lite overkill det hade fungerat lite bra utan. Grejen med singletonmönstret är att det bara får finnas EN instans av klassen db. Enda sättet att få den referensen är att anropa den statiska metoder Db::getInstance(); Användaren kan inte skapa en egen instans ($db = new Db(); funkar inte) för konstruktorn är privat. Dessutom är klassen abstrakt för att förtydliga. Det går nämligen inte att skapa instanser utav abstrakta klasser.
Hade jag tillåtit användaren att skapa egna instanser av db klassen hade antagligen koden i metoden connect() körts flera gånger vilket är ett slöseri eftersom den bla innehåller lite config och en sql fråga som bara ska köras en gång.

-----

Ja min klass retunerar ett PDO objekt vilket är samma som din gör. Flödet blir ungefär såhär:

$db = Db::getInstance();
$stmt = $db->prepare('
            SELECT user_id, username, password, email
            FROM user
            WHERE username = ? AND password = ?
            ');
        
$stmt->execute($arguments); 
return $stmt->fetchAll();

-----

Jag vet ingen bra svensk resurs på designmönster. Du borde ändå börja med att förstå mer om OO innan du börjar med det. Ett "mönster" är inget magiskt utan bara bra/fungerande generella lösningar som andra gjort inom OO och som du kan applicera i din egen kod. Här är en bra bok:
http://www.adlibris.com/se/product.aspx?isbn=0596007124

learnMedlem sedan jan. 20051 130 inlägg
#11

Stort tack för alla ingående beskrivningar! Jag ska nu förkovra mig i ämnet men kommer förmodligen snart tillbaka med fler frågor.

Tack

pulseMedlem sedan nov. 200373 inlägg
#12

colione skrev:

en abstrakt klass är en klass som det är tänkt att andra klasser ska ärva från och går därför inte att instantiera. däremot kan subklassen nyttja alla publika och protectade egenskaper ich metoder, det gäller även konstruktorn och magic methods. detta kan man dock påverka med keywordet final.

ett exempel på när en abstrakt klass kan användas är t.ex en klass som jar en output-metod sim skriver ut alla satta egenskaper och dess värden. nu kan du ha kkasser som hamtar info och bearbetar den och du slipper sha med samma output-funktion inskriven i dessa klasser.

just keywordsen tycker jag finns bra beskrivna i manualen.

jag skulle dock vilja varna för att använda singketons samt en överdriven mängd statics då det lät drar en tillbaka till funktionsbaserad prigramering, om än inkapslat i en klass.

i övrigt tycker jag att singletons är dumt att använda annars med. hur ofta kan du vara stensäker på att du inte behöver en annan instans av den klassen? ponera att din applikation blir beroende av två skilda databaser, vad gör du då? skapar en likadan singleton med annat namn och andra uppgifter? stanger din första instans och skapar en annan? om du senare behover anvanda info från db2 i db1 skapar du om den forsta resursen då? det är bättre att ha koll på sina objekt an att arbeta med singletons, tycker jag. kanske genom att jobba medett registry.

ni får ursäkta felstavningar och osammanhängande text, en p1 är väldigt icke-optimalt att wf-posta från.

Singletons har sina användningsområden. Det klassiska är väl databasanslutningar och loggar som man bara vill ha en av. Gör man sitt projekt ensam och har koll på varenda klass fungar ett register lika bra men i större projekt där man är fler som jobbar kan du inte garantera att andra använder din kod som du har tänkt från början.
Sen finns det de som överdriver och kör det mönstret på allting även där det inte behövs. Det är därför det kallas ett av de mest "missförstådda" designmönstrena :)

learnMedlem sedan jan. 20051 130 inlägg
#13

En liten fråga:

Vad ska man använda fetch() och while eller fetchAll() och foreeach? Någon prestandaskillnad eller så?

Btw: Zend-programmet var lite väl dyrt kanske... 400 dollars för 1 år...

colioneMedlem sedan juni 20014 421 inlägg
#14

pulse skrev:

Singletons har sina användningsområden. Det klassiska är väl databasanslutningar och loggar som man bara vill ha en av. Gör man sitt projekt ensam och har koll på varenda klass fungar ett register lika bra men i större projekt där man är fler som jobbar kan du inte garantera att andra använder din kod som du har tänkt från början.
Sen finns det de som överdriver och kör det mönstret på allting även där det inte behövs. Det är därför det kallas ett av de mest "missförstådda" designmönstrena :)

Användnigsområdena för singleton är försvinnande få. Bara för att det finns exempel på när det används innebär det inte att det är ett bra sätt att arbeta på. :) Jag ser hellre en klass med en kontruktor där du specar din connection, alternativt vilken log-fil du vill använda. Du bevisar bara min poäng när du säger "använder din kod som du har tänkt från början." du kan aldrig vara 100% säker på att det du har tänkt från början håller i längden. Det är bättre med kommunikation och dokumentation än att tvinga in folk i vissa mönster. Jag säger det igen: det är väldigt sällan du bara behöver ha en instans av ett objekt och bara för att du behöver det nu innebär det inte att du behöver det sen. T.ex om du skulle behöva skilja på en läs-/skriv db. Då har du helt plötsligt två exakt likadana singleton, enda skillnaden är configen.

Om du fortfarande vill vara säker på att användaren inte får mer än en connection per databas kan du ju t.ex köra med ett internt register i klassen. I denna hanterar connectionsen. Men det är inte heller bra.

146 ms totalt · 3 externa anrop · v20260731065814-full.30151723
0 ms — hämta forumlista (cache)
0 ms — hämta statistik (cache)
144 ms — hämta tråd, inlägg och bilagor (db)