webForumDet fria alternativet

PHP - klasser och OOP - why?

21 svar · 1 440 visningar · startad av learn

learnMedlem sedan jan. 20051 143 inlägg
#1

Jag planerar att bygga en större sajt och har ganska goda kunskaper inom PHP och MySQL men har aldrig fått kläm på OO-delen.

Nu hade jag tänkt sätta mig in klasser och objekthantering och har därför sökt runt en hel del på Google. Mycket läsning finns det ju i ämnet och jag tror nu jag förstår någorlunda hur det fungerar kodmässigt.

Vad jag dock inte lyckas begripa helt och hållet är VARFÖR jag ska börja använda klasser i stället för funktioner. Visst, jag inser skillnaderna, men inte riktigt fördelarna.

Jag hänger med i resonemanget kring separation av kod och layout/design (MVC: Model-View-Controller), men det tycker jag att jag klarar bra utan klasser och objekt - genom att köra funktioner och göra includes etc.

Nu VILL jag FÖRSTÅ varför jag ska använda klasser.

Så hjälp mig! Ge mig gärna konkreta exempel.

Tack :)

learnMedlem sedan jan. 20051 143 inlägg
#2

Någon som har något att säga om detta?

GeinMedlem sedan sep. 20004 849 inlägg
#3

Du har möjligheten att modelera ditt system grafiskt med UML innan du sätter dig ner och kodar. På så sätt kan det även vara lättare för kunden (som kanske inte är så tekniskt insatt) att förstå din tänkta lösning och själv komma med synpunkter.

Jag tycker själv att det är mycket enklare att få en vettig struktur när man skriver OO och det är ofta väldigt lätt att återanvända kod eller bygga ut sitt system i efterhand.

Jag har programmerat många mer eller mindre enkla webbapplikationer i PHP. För några veckor sedan började jag med ett nytt litet projekt men valde istället Java vilket tvingade mig att skriva OO även för här (även om PHP tillät mig, men inte styrde mig på samma sätt) och jag slås nu av hur pass mycket mer välstrukturerat det blev automatiskt. Om det har med OO att göra eller om det bara är så att jag själv skriver bättre applikationer låter jag vara osagt.

Vid väldigt små program kan det däremot vara aningens onödigt att skriva OO. T.ex. enklare arbetsskript skriver jag själv aldrig OO.

learnMedlem sedan jan. 20051 143 inlägg
#4

Tack för ditt svar.

Fler som har åsikter i ämnet??

spangoMedlem sedan juni 20006 147 inlägg
#5

UML är ett svagt argument för OOP eftersom det går att modellera icke-OOP grafiskt också. Många komponenter i UML har dessutom inget direkt med OOP att göra (användningsfall, tillståndsdiagram, etc).

Däremot är, som Gein säger, OOP det som i dagsläget ger bäst abstraktionsmöjligheter, vilket förenklar (men som dessvärre inte garanterar) mer modulariserad kod, vilket i sin tur gör det lättare att återanvända eller byta ut delar utan att behöva skriva om (särskilt mycket av) resten av systemet.

Det går att klara av mycket med hjälp av funktioner, smarta includes och en massa annat, men ofta blir det att man återuppfinner hjulet, och som de säger, när man gör det upptäcker man ofta till slut att ens eget hjul är fyrkantigt.

learnMedlem sedan jan. 20051 143 inlägg
#6

Tack spango. :)

Fler får gärna delta med sina kunskaper/erfarenheter!

TroxyMedlem sedan mars 20041 508 inlägg
#7

Här är lite punkter/åsikter från mig om OOP:

- Man kan gömma irrelevant programlogik för att skriva oberoende/abstrakt kod. Man skapar ett gäng publika objektfunktioner som utför massor med jobb "under huven". För mig är detta huvudsyftet med OOP, att man ska slippa tänka på detaljer och försöka se saker på ett neutralt sätt. Detta är mycket populärt för databaser och då kallas det för "Database Abstraction Layer" (PDO, ADOdb, etc.)

- Man får automatiskt en form av "namespace" som undviker namnkonflikter. Eftersom alla konstanter och funktioner kapslas in i klassens egna namnrymd så kan man importera klassen smärtfritt i olika projekt så länge själva klassnamnet är unikt. Sen slipper man globala variabler som rent allmänt är "bad practice" i programmeringsvärlden.

- Det finns sk. designmönster att följa som passar många situationer. Det betyder att man kan "låna" ett färdigt tankesätt och slipper fundera ut hela strukturen själv. Singleton är ett vanligt mönster.

- Möjlighet att skapa ett "interface" som underlättar att skriva kompatibel kod. Det säkerhetsställer att samma typ av objekt ser likadana ut på utsidan. Om man tex. bygger ett stort CMS så kan man erbjuda tredje part att skapa plugins. Så länge alla plugins implementerar samma interface så fungerar det nästan helt automagiskt.

- Vanliga och tråkiga saker kan automatiseras som t.ex. uppstädning. "Nya" PHP5 erbjuder OOP-godis som underlättar, då tänker jag framför allt på Magic Methods med dekonstruktorn som gör det möjligt att utföra saker när en klass dör, t.ex. stänga en databas eller annat som lätt glöms bort.

pulseMedlem sedan nov. 200384 inlägg
#8

En av de största fördelarna med att använda OOP är att det låter dig återanvända kod på olika sätt som inte är möjligt med funktionsbaserad programmering. Att återanvända (eller minska duplicering) leder till applikationer som är lättare att förstå, underhålla och utöka vilket är krav i alla större system.

Här är en bra tråd på sitepoint som diskuterar det här:
http://www.sitepoint.com/forums/showthread.php?t=59898#post439958

Några bra (och lättlästa) böcker om ämnet:
http://www.adlibris.com/se/product.aspx?isbn=1590599098
http://www.adlibris.com/se/product.aspx?isbn=0596007124
http://www.adlibris.com/se/product.aspx?isbn=0596008678&r=1

colioneMedlem sedan juni 20013 386 inlägg
#9

Troxy skrev:

- Vanliga och tråkiga saker kan automatiseras som t.ex. uppstädning. "Nya" PHP5 erbjuder OOP-godis som underlättar, då tänker jag framför allt på Magic Methods med dekonstruktorn som gör det möjligt att utföra saker när en klass dör, t.ex. stänga en databas eller annat som lätt glöms bort.

Nu nitpickar jag. Men ditt senare exempel med db-stänging i destructorn är egentligen onödigt om du inte unsetar objektet/på annat sätt anropar destruktorn. PHP stänger själv sin anslutning till DBn när exekveringen är klar. I php brukar det egentligen vara lite hugget som stucket om man stänger databasen i destruktorn då majoriteten (? i varje fall av det jag ser) av koden inte unsettar speciellt mycket av objekten de använder. Jag förstår dock var du vill komma.

learnMedlem sedan jan. 20051 143 inlägg
#10

Tack för alla svar! Jag har nu börjat mixa lite med klasser etc men har stött på problem med arv osv. Se gärna min andra tråd: Länk

learnMedlem sedan jan. 20051 143 inlägg
#11

Nu har jag suttit och pillat lite mer med klasser osv. Måste faktiskt erkänna att jag i några fall fortfarande inte förstår riktigt fördelen med OO PHP... :r Känn ibland så mkt enklare att köra som förr. Men som sagt, jag vill lära mig att förstå :)

I alla fall, jag har (mha wF) lyckats få ihop en klass som sköter databashanteringen. Den ser typ ut så här:

abstract class db
{
	private static $dsn = 'mysql:host=localhost;dbname=xxx';
	private static $username = 'xxx';
	private static $password = 'xxx';
	private static $instance = null;
	

	//////////////////////////////////////////////////////
	// Anslut till 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();
		}
	}

	//////////////////////////////////////////////////////
	// Skapa en anslutning
	public static function get_instance()
	{
		if (! isset(self::$instance))
		{
			self::connect();
		}
		
		return self::$instance;
	}
	

	//////////////////////////////////////////////////////	
	// Avsluta db
	public static function disconnect()
	{
		self::$instance = null;
	}
	

	//////////////////////////////////////////////////////
	// Kör fråga
	public static function query($query_type, $sql, $arguments)
	{
		$db = self::get_instance();
		$stmt = $db -> prepare($sql);
		$stmt -> execute($arguments); 
		
		// Fetch
		if ( $query_type == 'fetch' )
		{
			return $stmt -> fetch();
		}
		
		// FetchAll
		elseif ( $query_type == 'fetchAll' )
		{
			return $stmt -> fetchAll();
		}
		
		// No fetching
		else
		{
			return void;
		}
	}
}

För att göra en query i databasen gör jag helt enkelt så här:

$sql = '
SELECT *
FROM user
';
		
$result = db_handler::query('fetchAll', $sql, array());
									
return $result;

Själv är jag rätt nöjd, det funkar mkt bra. Ser det bra ut i era ögon?

Nu försöker jag bygga fler klasser, t.ex. en klass för användarna på min sajt. Jag kallar den User.

Efter lite funderande över vad jag behöver har jag kommit fram till följande:

class User
{
	private $stmt;
	private $arguments;
	private $result;

	
	//////////////////////////////////////////////////////
	// Hämta alla användare
	public function get_all_users()
	{				
		$sql = '
		SELECT *
		FROM user
		';
		
		$result = db::query('fetchAll', $sql, array());
									
		return $result;
	}
		

	//////////////////////////////////////////////////////
	// Hämta en användare mha id
	// $arguments: user_id
	public function get_username($arguments)
	{
		$sql = '
		SELECT *
		FROM user
		WHERE id = ?
		';
		
		$result = db::query('fetch', $sql, $arguments);
			
		return $result['username'];
	}

	//////////////////////////////////////////////////////	
	// Radera en användare
	public function delete_user(id)
	{
		$sql = '
		DELETE
		FROM user
		WHERE id = ?
		';
	
		db::query('', $sql, array($user_id));
		
		return void;
	}
	

	//////////////////////////////////////////////////////	
	// Lägg till en användare
	public function add_user()
	{
		$sql = '
		INSERT
		INTO user (username, psw)
		VALUES (?, ?)
		';
		
		$arguments = array($_POST['username'], $_POST['psw']);
	
		db::query('', $sql, $arguments);
		
		return void;
	}
}

För att t.ex. hämta alla användare gör jag alltså så här:

$users = new User;

$users->get_all_users();

foreach ($users as $user)
{
	echo $user['username'];
}

Detta funkar ju också bra, men jag förstår verkligen inte varför detta inte lika gärna kan ligga i vanliga funktioner...

Kan inte någon guida mig rätt och på något enkelt sätt rätta mig och förklara. Ta gärna med exempel på hur jag ska göra och förstå!

Tack för ert tålamod, jag vill verkligen! :bire

learnMedlem sedan jan. 20051 143 inlägg
#12

Kom igen nu alla PHP-experter...! ;)

Håller ni inte med om att klassen ovan känns lite onödig och felkonstruerad...eller? Jag måste ju tänka på fel sätt, hjälp mig.

learnMedlem sedan jan. 20051 143 inlägg
#13

Bland annat har jag lite svårt att förstå detta när mitt objekt, säg $user, innehåller flera användare (get_all_users()). Ett objekt som innehåller en massa objekt blir det ju, typ...

Jag vill ju dels kunna hämta en användare och information kring denna, men också kunna hämta alla användare och t.ex. lista dem i en tabell... Hur brukar ni göra detta med klasser?

Svårt att förklara hur jag menar. Kanske någon förstår mig i alla fall? :)

learnMedlem sedan jan. 20051 143 inlägg
#14

Detta var tydligen svår nöt att knäcka... Jag har inte heller någon aning.

colioneMedlem sedan juni 20013 386 inlägg
#15

Jag tror att du fortfarande är kvar lite i procedurella tänket. :) Istället för att ha en User-klass som returnerar n antal users borde du kanske ha en klass som står för databasinteraktionen för alla users. Denna returnerar sedan varje användare som en instans av ett User-objekt som innehåller egenskaperna och metoderna för varje User. Du kan även returnera speciella user-objekt beroende på behörighet, där en administrator ärver av usern, men att den även har andra metoder och egenskaper.
Du skulle även kunna ha en UserCollection-class som du kan innehålla flera users som du kan iterera över, plocka ur värden ur/ändra. Om du har ändrade värde kan du då t.ex anropa en save()-metod på en User där denna kommunicerar till Users specifika databas-klass (som kanske ärver av en generellare klass som andra objekt kan extendat till en kommunikationsklass) och berättar för dem att det hr objektets egenskaper ska sparas i en databas.
Om du befinner dig i en UserCollection kan du ha en save()-metod på den, där du då itererar över alla User-objekt och anropar dess save()-metod.

Det finns väldigt mycket att lära om OOP och design patterns. Mitt exempel här ovan är bara snabbt taget ur skallen och behöver inte vara bäst lämpat för vad du har i åtanke, eller något annat för den delen heller.
Vad jag tror att du måste göra är att sätta dig med en bok, eller annan literatur, som avhandlar OOP, allra bäst är om du läser en specifikt för OOP och en som tar upp Design Patterns generellt. Jag har nämnt ett par sådana böcker i tidigare trådar. Söker du efter Head First borde du hitta nån sådan post.

Att bli en duktig programmerare är inte något som sker över 2 veckor och 10 forumposter. Enda sättet är att skriva kod och mer kod. Som programmerare är det bra att göra fel, att kunna skriva något som man sen inser att här tänkte jag fel eller det här kunde jag inte då. Visserligen blir de tillfällen färre med åren ;) men att stöta och blöta är the shit när det gäller programmering.

EDIT -> Böckerna jag pratade om tar pulse upp i sitt inlägg.

Misstolka mig rätt, vi hjälper gärna till - både med kodfrågor och när du inte förstår, men räkna inte med att folk kommer sammanfatta alla deras erfarenheter vad gäller OOP i ett eller ett par inlägg. :)

colioneMedlem sedan juni 20013 386 inlägg
#16

Nu blir det ett inlägg till.

Vad gäller din DB-klass har du kommit in i det mönstret jag varnade för i din andra tråd[1], du har kapslat in funktioner i ett namespace som heter db och anropar dem som om det skulle vara som vanligt om en med 4 extra tecken (db::). Jag förstår att nästkommande frågor kan vara svåra att svara på, men försök att motivera:

  • Varför har du implementerat din databasklass som en singleton och förstår du vad singleton innebär?
  • Varför är alla funktionerna i din db-klass public statics?
  • Vad är det som gör att alla funktionerna i db-klassen ska vara public static, medan de i user-klassen inte är det?

Det är jättebra att du återanvänder kod som du hittar här på wF, ännu bättre är det om du sätter dig ner, läser igenom, provar koden och frågar dig varför gör han såhär? Förstår jag allt som händer? Finns det bättre sätt? :)

[1] http://www.webforum.nu/showthread.php?p=1426472#post1426472

learnMedlem sedan jan. 20051 143 inlägg
#17

colione, stort tack för dina svar och ditt engagemang!

Jag tror att du fortfarande är kvar lite i procedurella tänket.

Tror jag också ;)

Istället för att ha en User-klass som returnerar n antal users borde du kanske ha en klass som står för databasinteraktionen för alla users. Denna returnerar sedan varje användare som en instans av ett User-objekt som innehåller egenskaperna och metoderna för varje User.

Ok, låter lite bättre. Men det blir ju en himla massa objekt, ca 200 stycken, i listan. Om jag använder en funktion/metod som denna:

    // Hämta alla användare
    public function get_all_users()
    {                
        $sql = '
        SELECT *
        FROM user
        ';
        
        $result = db::query('fetchAll', $sql, array());
                                    
        return $result;
    }

Så returneras ju en array med alla users och jag kommer åt deras uppgifter på detta vis: $user['username']. Genom att istället skapa flera hundra userobjekt med en massa egenskaper så kommer jag åt det samma genom detta: $user->get_username(). Kan du ge mig ett konkret exempel på fördelen med objekt-varianten? Det blir ju också en massa metoder, t.ex. get_username(), get_email(), get_address(), get_firstName(), get_lastName(), get_phone() etc etc. Eller hur kan man lösa man det?

Ge mig gärna exempel på de två klasserna: User och AllUsers

Det där med save() diggar jag!

Om du har ändrade värde kan du då t.ex anropa en save()-metod på en User där denna kommunicerar till Users specifika databas-klass (som kanske ärver av en generellare klass som andra objekt kan extendat till en kommunikationsklass) och berättar för dem att det hr objektets egenskaper ska sparas i en databas. Om du befinner dig i en UserCollection kan du ha en save()-metod på den, där du då itererar över alla User-objekt och anropar dess save()-metod.

UserCollection-klassen förstår jag inte riktigt. Beskriv gärna lite närmare.

Att bli en duktig programmerare är inte något som sker över 2 veckor och 10 forumposter.

I get that ;) Jag provar mig fram.

learnMedlem sedan jan. 20051 143 inlägg
#18

colione skrev:

Nu blir det ett inlägg till.

Vad gäller din DB-klass har du kommit in i det mönstret jag varnade för i din andra tråd[1], du har kapslat in funktioner i ett namespace som heter db och anropar dem som om det skulle vara som vanligt om en med 4 extra tecken (db::). Jag förstår att nästkommande frågor kan vara svåra att svara på, men försök att motivera:

  • Varför har du implementerat din databasklass som en singleton och förstår du vad singleton innebär?
  • Varför är alla funktionerna i din db-klass public statics?
  • Vad är det som gör att alla funktionerna i db-klassen ska vara public static, medan de i user-klassen inte är det?

Det är jättebra att du återanvänder kod som du hittar här på wF, ännu bättre är det om du sätter dig ner, läser igenom, provar koden och frågar dig varför gör han såhär? Förstår jag allt som händer? Finns det bättre sätt? :)

Jag vill ju endast ha en instans(koppling) till databasen. Som jag försått det så bygger singleton på detta. Jag slipper gärna hålla på att skapa objekt av klassen ($db = new db). Det känns helt enkelt som en "genväg" att direkt skriva db::query('fetchAll', $sql, array()). Vad gäller min gamla Userklass så stämmer ju ingenting, den behöver vi inte bry oss om... :)

pulseMedlem sedan nov. 200384 inlägg
#19

Du har stött på det gamla problemet att mappa ihop en relationsdatabas med objektorienterad kod. De två brukar inte gå så bra ihop utan man får som programmerare ta till olika knep för att mappa ihop dem. Det du har börjat med i din User class kallas "table data gateway" och den innehåller alltså alla sql sql-frågor till User-tabellen i databasen. Om jag skulle göra en sådan klass skulle den se ut ungefär såhär:

class UserGateway{

	/** @var PDO */
	private $db;

	function __construcT(){
		$this->db = Db::getConnection();
	}

	/** Det här är de vanliga metoderna som brukar finnas i sådana här klasser. Om du lägger dem i en superklass kan du
	* återanvända dem mellan alla gateways med arv (en sådan sak är svår att göra med funktionsbaserad kod)
	*/
	
	function findAll(){
		//select * from user
	}

	function find($id){
		//select * from user where id = $id
	}

	function update($id, $name){
		//update users set name = $name where id = $id
	}

	function insert($name, $age){
		//insert into users...
	}

	function delete($id){
		//delete from...
	}

	//man kanske även vill lägga till lite unika metoder för usergatewayn

	function login ($username, $password){
		$row = "select * from users where username = $username and password = $password";
		return $row ? $row : false;
	}

	function findWithName($name){
		//select ... where name = $name
	}
}
//och man använder den såhär
$gateway = new UserGateway();
$user = $gateway->findWithName("kalle");
$gateway->update($user->id, "Pelle");

Gatewayn är väldigt enkel att sätta upp och använda. Enligt min erfarenhet behöver man oftast inte skapa speciella "userklasser" osv utan det går bra att låta gatewayn retunera raderna direkt från databasen. I de flesta fall vill du ändå bara skriva ut dem direkt på sidan och behöver du manipulera dem lite kan man göra det alldeles innan eller under utskriften.

---

Ett alternativ till Gatewayn är active record. Då skapar man tex en User klass och sen lägger man in både den affärslogik och den sql som krävs i samma klass.

class User{

	private $id;
	private $name;

	//*** Affärslogik
	function __construct($id = null, $name){
		$this->id = $id,
		$this->name = $name;
	}

	function setName($name){
		$this->name = $name;
	}

	function getName(){
		return $this->name;
	}

	function getId(){
		return $this->id;
	}

	function speak(){
		return sprintf("Hello my name is %s", $this->name);
	}

	//*** SQL Anrop

	//observera static
	static function find($id){
		//User result = select * from person where id = $id;
		//return result;
	}

	function save(){
		if($this->id){
			$this->update();
		}
		else{
			$this->insert;
		}
	}

	private function insert(){
		//insert into user (name) values $this->name;
		$this->id = lastInsertedId();
	}

	private function update(){
		//update user set name = $this->name where id = $this->id
	}
	
	function delete(){
		if(! $this->id){
			return false;
		}
		//delete from users where id = $this->id;
	}
}
//används såhär:
//ny user
$user = new User("Kalle");
$user->save();
echo $user->getId();

//hämta användare
$user = User::find(2); //statiskt anrop
$user->speak();
$user->setName("Pelle");
$user->save();

Det finns en "bibel" av Martin Fowler som tar upp flera sådana här "mönster":
http://www.adlibris.com/se/product.aspx?isbn=0321127420

Läs den efter du läst de andra böckerna som jag tipsade om ovan. Den tar upp extremt många bra saker som du bör känna till för att skriva bra objektorinterade webbapplikationer tex hur man delar in sin applikation i olika lager (MVC), hur man arbetar mot databaser i OO osv. Om du är seriös att lära dig OOP måste du läsa mycket och sen sätta dig ner och koda själv det du läst... det finns inga genvägar.

---

Några andra synpunkter:

Jag har sett flera gånger att du deklarerar variabler längst upp i klassen (klassvariabler) som heter samma sak som dina argument i funktionerna.

class User
{
	private $stmt;
	private $arguments;
	private $result; 

	public function get_username($arguments)

Gör inte så. Klassvariabler kommer du bara åt genom att använda $this och inget annat.

class User
{
	private $test = "hej";
	public function get_username($arguments){
		echo $this->test;
	}
}

Skriv alla kommentarer på engelska och följ de standardarer som jag refereade till i ett annat inlägg. En av de viktigaste egenskaperna hos en bra programmerare är att denne är tydlig så att andra (och han själv) kan följa hans kod. Ytterligare en bra bok av Fowler hur man skriver tydlig kod finns här:
http://www.adlibris.com/se/product.aspx?isbn=0201485672

Jag säger det igen: Köp böckerna du fått tips om och läs dem innan du börjar med mer avancerade saker som designmönster och sånt. Glöm allt vad singleton osv heter för du kan omöjligt förstå när och varför man ska använda den om du inte kan grunderna och alla begrepp först. När du skriver db::query('fetchAll', $sql, array()) har det ingenting med singeltons att göra utan det där är bara ett anrop till en statisk metod. Tanken med singleton är att den garanterar att de bara existerar en instans av en klass, men som sagt jag tycker inte du ska bry dig nu ändå. Förresten finns det bra mycket roligare och mer användbara designmönster än singletons :)

learnMedlem sedan jan. 20051 143 inlägg
#20

pulse, tack för ditt svar och dina tips!

Ska nog ta och köpa ett par böcker. Om du satt i min sits och skulle välja två lättlästa böcker som täcker detta område så bra som möjligt, vilka skulle du välja?

Blir bra kvälls- och nattläsning :)

Genererad på 392 ms · cache AV · v20260730165559-full.f96bc7eb