Jump to content

[TUT: PHP] Klassen, deel 3: Klassen in praktijk 1


marcootje

Recommended Posts

PHP: Klassen



Deel 3: Klassen in praktijk 1

Deel 1: De basis

Deel 2: Basis uitbreiding

Deel 3: Klassen in praktijk 1

Deel 4: Klassen in praktijk 2

Introductie

Je vorige site in php werd op een gegeven moment een grote hooiberg. Overal stond code, je wist niet meer waar je moest zoeken. Aanpassingen waren nauwelijks mogelijk, en eigenlijk stond heel veel dubbel in je website, maar net weer een tikkie anders. Deze keer wil je het beter aanpakken, maar hoe?

Hiervoor kun je klassen gebruiken, waarmee je een structuur aanbrengt in je site. Je krijgt aparte klassen voor elk aparte onderdeel, hierbij kun je denken aan klassen voor je database, de gebruiker, de taal, error-handling, het systeem, de layout, etc...

In dit deel van de tutorial ga ik overerving uitleggen, en voorbeelding in de praktijk geven.

Herhaling

Als je dat nog niet gedaan hebt en nog weinig van klassen afweet, neem dan deel 1 en/of deel 2 van deze tutorial door!

Deel 3

In dit deel ga ik uitleggen hoe je klassen in praktijk kunt gebruiken. Ik maak gebruik van meerdere voorbeelden

Bij dit deel heb je een host met php-ondersteuning nodig, en een (tijdelijke) map of mappen waarin je dit project kun uitvoeren.

LET OP: in tegenstelling tot voorgaande delen ben je er nu niet van verzekerd dat de voorbeelden ook daadwerkelijk werken (tijdgebrek). De voorbeelden zijn ook echt bedoeld als voorbeeld, niet om een systeem weg te geven aan jan en alleman.

Hierna komt nog een deel 4, waar nog een paar kleinere praktijk-voorbeelding komen!

Voorbeeld in praktijk #1

Principe en benodigde bestanden

We gaan een systeem maken voor een nieuwe, uitgebreide website. Dit gaan we doen door middel van verschillende klassen die we aan elkaar hechten.

De aparte klassen die we gaan maken zijn voor de database, de gebruiker, de taal, error-handling, het systeem, functies en de layout. Sommige van deze zijn optioneel, en hoef je niet per se in je eigen project te stoppen.

In deze tutorial gaan we er van uit dat we ze allemaal meenemen, maar de database, error-handling en het systeem zijn in ieder geval 'verplicht'.

Ik ga geen complete scripts geven, alleen een soort framework; een idee van hoe het moet. De rest mag je zelf invullen.

We noemen voor het gemak de map die je voor deze tutorial hebt aangemaakt ROOT. Dit is niet de officiele root, maar we noemen hem gewoon even zo.

In de map ROOT maak je een map genaamd 'includes', natuurlijk zonder de ' '. In deze map maken we het bestand init.php aan, en een map 'classes'. In de map classes maak je de bestanden voor de klassen aan. Hieronder volgt een overzicht.

  • ROOT/
    • includes/
      • init.php
      • functions.php
      • classes/
        • database.class.php
        • handler.class.php
        • language.class.php
        • system.class.php
        • template.class.php
        • user.class.php

We gaan het systeem zo maken dat je maar 1 bestand hoeft aan te roepen (init.php), en vanuit dat bestand elk ander bestand kunt opzetten.

We gebruiken in dit systeem klassen met hoofdletters, omdat ik dit gewend ben. Dit kun je natuurlijk naar eigen voorkeur aanpassen.

database.class.php

We beginnen met de database. De database is onafhankelijk van andere klassen, dus makkelijk in elkaar te zetten.

Waarom een database-klasse?

Stel je gebruikt nu mysql_query() en verwante functies, samengevat de mysql_*-functies.

Op een gegeven moment wil je 'opeens' iets anders dan mysql gebruiken, of is mysql simpelweg niet beschikbaar op je host.

Daar zit je dan met je mysql_*. Geen probleem toch? Dan herschrijf je de mysql_*-functies even.

Maar dat is natuurlijk niet erg netjes, en kan bovendien nog problemen geven ook.

Dus je maakt een database-klasse, waarmee je heel simpel kunt overstappen op een ander database-type.

Dit kun je op 2 manieren doen.

1). Je houdt de mysql_*-functies aan, voor elke mysql_*-functie maak je een klasse-functie:

<?php
class DB { // DB of db staat voor database
public function __construct() {
	mysql_connect(/*naam, ww, etc..*/);
	mysql_select_db(/*db*/);
}

public function query($query) {
	return mysql_query($query);
}

public function fetch_assoc($query) {
	return mysql_fetch_assoc($query);
}

/* etc... */
}
?>

Dit is in ieder geval al beter aanpasbaar dan overal verspreid mysql_* hebben staan.

2). Je maakt eigen functies, afwijkend van bijvoorbeeld mysql_*:

<?php
class DB { // DB of db staat voor database
public function __construct() {
	mysql_connect(/*naam, ww, etc..*/);
	mysql_select_db(/*db*/);
}

public function select($query) {
	$result = mysql_query($query);
	$return = array();
	while($res = mysql_fetch_assoc($result)) {
		$return[] = $res;
	}
	return $return;
}

public function edit($query) {
	mysql_query($query);
	return mysql_affected_rows();
}

/* etc... */
}
?>

Hiermee krijg je, zoals je ziet, bij select() meteen álle resultaten, en bij edit() krijg je terug hoeveel rijen er bewerkt zijn.

We gebruiken in de volgende bestanden de 2e manier.

De bovenstaande codes zijn overigens nog niet compleet, ze kunnen nog goed uitgebreid worden. Voor de tutorial is het echter genoeg.

Zoiets zet je in je database-bestand, en dan gaan we naar ons user-bestand.

user.class.php

Dit bestand heeft geen standaard layout, behalve dat er een 'class User' in moet zitten. In deze klasse haal je informatie op over de gebruiker, als we een gebruiker hebben en het dus geen gast is. Het kan ook als het een gast is, dit hangt weer volledig af van het systeem en van de website. Alle informatie over de gebruiker gaat dus via deze klasse, waardoor informatie niet te vaak wordt opgevraagd van de database-server.

Deze klasse is afhankelijk van de database-klasse, omdat je waarschijnlijk informatie over de gebruiker uit de database wilt halen.

Het zal er ongeveer zo uit zien:

<?php
class User {
private $db;
private $info;

public function __construct($_db) {
	$this->db = &$_db;
	$this->updateInfo();
}

public function updateInfo() {
	// update user-informatie
	$info = $this->db->select("query");
	$this->info = $info[0];
}

public function getInfo($key) {
	return $this->info[$key];
}

/* etc... */
}
?>

handler.class.php

In dit bestand regelen we wat er moet gebeuren was er een error op komt zetten. Dit is alleen een deel van de errors, met bijvoorbeeld parse errors wordt het hele bestand niet uitgevoerd, dus deze klasse ook niet.

Als je de standaard error handler van php wilt gebruiken, moet je deze natuurlijk overslaan.

Het principe van de handler is dat we foutmeldingen in de database stoppen. Het voordeel is dan dat we alle errors binnenkrijgen, ook die van 'gewone' gebruikers. Het nadeel is dat het niet echt handig werkt met developpen. Daarom planten we er ook een debug-schakelaartje in.

Deze klasse is afhankelijk van de database- en de user-klasse, want we willen de informatie van de foutmeldingen in de database stoppen, inclusief het user-id van de gebruiker.

Hier is een voorbeeldje:

<?php
class Handler {
private $db;
private $user;
public $b_debug;
public $a_noRedirectErrors = array('403', '404');

public function __construct($_db, $_user) {
	$this->db = &$_db;
	$this->user = &$_user;

	$this->b_debug = true; //true: errors weergeven, false: errors loggen

	set_error_handler(array(&$this, "ErrorLogger")); //ErrorLogger instellen als error handler
	set_exception_handler(array(&$this, "ExeptionLogger")); //Exeptionlogger instellen als exception handler
}

// error handling
public function ErrorLogger($errno, $errstr, $errfile, $errline) {
	switch($errno) {
		case E_ERROR:				$errtype="Error";						break;
		case E_WARNING:				$errtype="Warning";						break;
		case E_PARSE:				$errtype="Parse Error";					break;
		case E_NOTICE:				$errtype="Notice";						break;
		case E_CORE_ERROR:			$errtype="Core Error";					break;
		case E_CORE_WARNING:		$errtype="Core Warning";				break;
		case E_COMPILE_ERROR:		$errtype="Compile Error";				break;
		case E_COMPILE_WARNING:		$errtype="Compile Warning";				break;
		case E_USER_ERROR:			$errtype="User Error";					break;
		case E_USER_WARNING:		$errtype="User Warning";				break;
		case E_USER_NOTICE:			$errtype="User Notice";					break;
		case E_STRICT:				$errtype="Strict Notice";				break;
		case E_RECOVERABLE_ERROR:	$errtype="Recoverable Error";			break;
		default:					$errtype="Unknown error (".$errno.")";	break;
	}
	$referrer=(isset($_SERVER['HTTP_REFERER']))? $_SERVER['HTTP_REFERER'] : __FILE__;

	if($this->b_debug) {
		if(!in_array($errstr, $this->a_noRedirectErrors)) {
			echo($errtype.": ".$errstr." op regel ".$errline." in ".$errfile."<br />\n");
		}
		return true;
	}

	$this->db->edit("insert query");

	if(!in_array($errstr, $this->a_noRedirectErrors)) {
		@header("Location: ../errors/error.php") or die("Er is een fout opgetreden bij het laden van de pagina.<br>De fout is bij de admins gemeld.<br>");
	}
	return true;
}

// exeption handling
public function ExeptionLogger($e) {
	$s_trace = $e->getTraceAsString();
	$referrer=(isset($_SERVER['HTTP_REFERER']))? $_SERVER['HTTP_REFERER'] : __FILE__;

	echo '
	<span style="font-size: 150%; font-weight: bold;">Database fout:</span><br>
	<br>
	<b>Melding:</b> '.$e->getMessage().'<br>
	<b>Bestand:</b> '.$e->getFile().' op regel '.$e->getLine().'<br>
	<b>Pad:</b> <br>'.nl2br($s_trace).'<br>
	';

	if(!$this->b_debug) {
		$errstr = 'Melding:<br>'.$e->getMessage().'<br><br>Pad:<br>'.nl2br($s_trace);
		$this->db->edit("insert query");
		echo '<br>
		Er is een fout opgetreden bij het laden van de pagina.<br>
		De fout is bij de admins gemeld.<br>';

	}

	return true;
}
}
?>

De queries heb ik er uitgehaald, want je moet er zelf ook nog wat aan doen. Ook is de tabel-structuur van iedereen toch een tikkie anders.

Ik zou wel deze structuur aanbevelen:

veld type variabele in Handler::ErrorLogger variabele in Handler::ExeptionLogger
id int(11) - -
u_id int(11) $this->user->info['id'] $this->user->info['id']
status int(1) default 0 - -
onderzoeker int(11) - -
datum int(11) time() time()
type varchar(64) $errtype 'Exeption'
bestand varchar(256) $errfile $e->getFile()
regel int(11) $errline $e->getLine()
msg text $errstr $errstr
ip varchar(32) $_SERVER['REMOTE_ADDR'] $_SERVER['REMOTE_ADDR']
request_uri varchar(256) $_SERVER['REQUEST_URI'] $_SERVER['REQUEST_URI']
referer varchar(256) $referrer $referrer

Hier kun je ook een mooi administratie-systeem op bouwen, zodat je mooi kunt zien welke errors er zijn opgedoken. Hiervoor zijn de velden 'status' en 'onderzoeker'. Als je niet wilt weergeven wat een status van een error is (dus: open, mee bezig, afgehandeld) of wie er eventueel met de error bezig is, kun je dit weglaten. Voor de volledigheid heb ik het erbijgezet.

Je kunt hiervoor zelf je queries in elkaar zetten met je eigen database-systeem in je achterhoofd.

system.class.php

Dit is het systeem wat altijd benodigd is. In tegenstelling tot veel andere websites komt het systeem hier niet als eerste. Dit is puur omdat het niet nodig is, en het dus handiger is om het systeem na de error handling te doen (voor het geval er een fout zit in de system-klasse die niet altijd tevoorschijn komt).

De system-klasse kan dus ook eerder geplaatst worden. Ikzelf heb in de system-klasse ook nog wat user-gerelateerde zaken staan, dus heb ook de user-klasse nodig. Maar dit zal dus niet bij iedereen noodzakelijk zijn. (De user-gerelateerde zaken zijn in het voorbeeld weggehaald)

In de system-klasse komen de instellingen voor de website, en een paar grondfuncties. Hierbij kun je bijvoorbeeld denken aan een redirecter, en land- en taal-instellingen.

<?php
class System {
private $db;
public $cat;
public $page;

public function __construct($_db)
	{
	// nodige instellingen
	error_reporting(E_ALL);
	setlocale(LC_ALL,'nl_NL') or setlocale(LC_ALL,'nld_NLD');
	date_default_timezone_set("Europe/Brussels");
	session_start();

	$this->db = &$_db;

	// queries met betrekking tot het verlopen van sessies
	// deze queries zet ik neer ter voorbeeld, het kan zijn dat ze totaal niet bij je database-structuur passen
	$limiet = time()-(10*60);//10 minuten
	$this->db->edit("UPDATE users SET ingelogd=0 WHERE laatstIngelogd<".$limiet);

	// extra:
	// je kunt hier bijvoorbeeld nog de bestandsnaam uitpluizen of iets dergelijks: (nodig voor template)
	preg_match("%.*?/([^/]+?)/([^/]+?\.php)%", $_SERVER['SCRIPT_NAME'], $url);
	$this->cat = $url[1]; // categorie binnen de website
	$this->page = $url[2]; // bestandsnaam.php
	}

public function redirect($s_relativePath, $b_exit=TRUE) {
	$host  = $_SERVER['HTTP_HOST'];
	$uri   = rtrim(dirname($_SERVER['PHP_SELF']), '/\\');
	$extra = $s_relativePath;
	header("Location: http://$host$uri/$extra");
	if($b_exit) {
		exit; // het script op laten houden, anders zou het script doorgaan na de redirect
	}
}
}
?>

template.class.php

Als je een grote website hebt, wil je misschien ook meerdere templates/layouts online hebben staan. Dit kun je doen door middel van een template-systeem. Als je een template-systeem hebt, kun je in verschillende mappen verschillende layouts maken, waarop dezelfde website draait. Je hoeft dan dus niet de hele site te kopieren om een nieuwe layout online te zetten, en vooral: je hoeft met 10 layouts niet 10 bestanden te bewerken voor een kleine wijziging.

Typische functies voor een template-systeem zijn init(), setVar() en parse().

In onderstaand voorbeeld gebruiken we __construct() voor de initialisatie.

De klasse Template is afhankelijk van het systeem (variabelen cat en page) en de gebruiker (variabele template).

<?php
class Template {
private $system;
private $user;

private $phpVars;
public $s_templatePage;
public $s_includePage;

public function __construct($_system, $_user, $s_templatePage) {
	$this->system = &$_system;
	$this->user = &$_user;

	$this->s_templatePage = &$s_templatePage;

	$this->menuItems();
	$this->phpVars = array('head_info'=>'', 'debug'=>'Debug info:<br>');

	if($this->s_templatePage != '') {
		$s_page = "/layout/".$this->user->info['template']."/templates/".$this->system->cat."/".$this->s_templatePage.".php";
	}
	else {
		$s_page = "/layout/".$this->user->info['template']."/templates/".$this->system->cat."/".$this->system->page; // $this->system->page bevat al ".php"!
	}

	// layout importeren
	if(string_in_array(str_replace("/", "\\", $s_page), get_included_files())) {
		die("Layout mag maar 1 keer geparsed worden.<br>");
	}
	if(!file_exists("..".$s_page)) {
		die("Het template-bestand \"".$s_page."\" bestaat niet.");
	}

	$this->s_includePage = "..".$s_page;
}

public function setVar($varName, $varValue=NULL) {
	$this->phpVars[$varName] = $varValue;
	return true;
}

public function parse() {
	// copy system-vars
	$vars = &$this->phpVars;
	$_user = &$this->user;

	// print to screen
	require("layout/".$this->user->info['template']."/templates/".$this->system->cat."/header.php");
	require_once($this->s_includePage);
	require("layout/".$this->user->info['template']."/templates/".$this->system->cat."/footer.php");
}
}
?>

Zorg er wel voor dat de bestanden en mappen ook daadwerkelijk bestaan (aanpassen in de code kan natuurlijk ook... ;) ) .

language.class.php

Met een taal-systeem kun je meerdere talen gebruiken op je website. De zinnetjes worden in een apart bestand gezet, welke vertaalt kunnen worden. Je zet de zinnetjes in bijvoorbeeld een multi-dimensionale php-array zetten, in een xml-bestand, of in een SQL-database.

Typische functies voor een language-klasse zijn:

- __construct($_user): vanuit $_user (de user-klasse) of een cookie bepaal je de taal, en importeer je het bestand

- get($sleutel, $vars): verkrijg een zin

Hieronder geef ik een klein voorbeeld, met een multi-dimensionale php-array.

<?php
class Language {
private $user;

public $language;
public $a_lang;

public function __construct($_user) {
	$this->user = &$_user;

	$this->language = $this->user->info['taal'];

	if(!file_exists("../includes/languages/".$this->language.".php"))
		die("Het taalpakket '".$this->language."' bestaat niet!");

	require_once("../includes/languages/".$this->language.".php");
	/* in dit bestand staan de zinnen in zo'n soort array:
	$_lang = array(
		'forum' => array(
			'login' array(
				'login'=>'Inloggen',
				'username'=>'Gebruikersnaam',
				'password'=>'Wachtwoord'
			)
		),
		'articles' => array(
			'article'=>'artikel'
		)
	);

	*/
	$this->a_lang = $_lang;
}

public function get($sleutel, $vars) {
	// EEN BETERE manier vinden om van de $sleutel (bijvoorbeeld forum/login/username) naar bijv. $_lang['forum']['login']['username'] te gaan, bijvoorbeeld:
	$sleutel = explode('/', $sleutel);
	$return = $this->a_lang;
	foreach($sleutel as $sl) {
		$return = $return[$sl];
	}

	foreach($vars as $key => $var) {
		$return = str_replace($key, $var, $return);
	}
	return $return;
}
}
?>

init.php en functions.php

functions.php mag duidelijk zijn, daar komen losse functies in.

In init.php worden alle klassen bij elkaar gevoegd:

<?php
// algemene klasse Pagina
class Pagina { // standaard klasse, alle klasses moeten deze uitbreiden!!! ( class Index extends Pagina )
public $db;
public $system;
public $handler;
public $user;
public $template;
public $language;

public function __construct($s_templatePage='') {
	// $s_templatePage moet geset worden als je een bepaalde template wilt laden, anders dan de bestandsnaam van het aangeroepen bestand

	// include functions
	require_once("includes/functions.php");

	// include classes
	require_once("includes/classes/database.class.php");
	require_once("includes/classes/user.class.php");
	require_once("includes/classes/handler.class.php");
	require_once("includes/classes/system.class.php");
	require_once("includes/classes/template.class.php");
	require_once("includes/classes/language.class.php");

	// call classes
	$this->db = new DB();
	$this->user = new User(&$this->db); // met & maak je een referentie naar de variabele, in plaats van een kopie. Een wat? google -> php variable reference, zoek maar op zoiets;) 
	$this->handler = new Handler(&$this->db, &$this->user);
	$this->system = new System(&$this->db); // kan evt. boven handler
	$this->template = new Template(&$this->system, &$this->user, &$s_templatePage);
	$this->language = new Language(&$this->user);
}
}
?>

Gebruik van het systeem

Het gebruik van het systeem dat hierboven beschreven is, is vrij simpel geworden. In deel 2 van deze tutorial is uitgelegd wat overerving precies is. Dat doen wij hier precies:

php-bestand

<?php
require("../includes/init.php");

class Index extends Pagina {
function __construct() {
	parent::__construct();
	$this->index();
	$this->template->parse();
}

function index() {
	$this->template->setVar("HelloWorld", $this->language->get('test/test1/helloWorld'));
}
}

$index = new Index();
?>

template-bestand

<?=$vars['HelloWorld']?>

met in het taal-bestand:

<?php
$_lang = array(
'test' => array(
	'test1' => array(
		'helloWorld' => 'Hallo Wereld!'
	)
)
);
?>

geeft uiteindelijk het gewenste resultaat:

Hallo Wereld!

Een wat uitgebreidere pagina:

php-bestand

<?php
require("../includes/init.php");

class Index extends Pagina {
function __construct() {
	parent::__construct();
	$this->index();
	$this->template->parse();
}

function index() {
	$this->template->setVar("laatstIngelogd", date('d-m-Y H:i:s', $this->user->info['laatstIngelogd']));
	$nieuws = $this->db->select("SELECT * FROM nieuws");
	$this->template->setVar("nieuws", $nieuws);
	$this->template->setVar("LANG_nieuws", $this->language->get('nieuws/nieuws'));
	$this->template->setVar("LANG_lastLogin", $this->language->get('nieuws/lastLogin'));
	$this->template->setVar("LANG_author", $this->language->get('nieuws/author'));
	$this->template->setVar("LANG_message", $this->language->get('nieuws/message'));
}
}

$index = new Index();
?>

template-bestand

<?=$vars['LANG_lastLogin']?>Laatst ingelogd: <?=$vars['laatstIngelogd']?><br>

<?=$vars['LANG_nieuws']?>:<br>

<?php
foreach($vars['nieuws'] as $nieuw) {
?>

<?=$vars['LANG_auteur']?>: <?=$nieuw['auteur']?><br>

<?=$vars['LANG_message']?>:<br>

<?=$nieuw['bericht']?>

<?php
}
?>

Einde deel 3

Dit was deel 3 van deze tutorial. In het laatste deel, deel 4 komen nog wat kleine voorbeelden. Daarna is het op met de pret!

Beoordelingen, correcties en tips zijn welkom!

NB: De voorbeelden zijn niet bedoeld om klakkeloos te kopieren, je moet je koppie erbij hebben. Het is niet uitgesloten dat er foutjes in zitten, deze hebben vooral te maken met verschillen in uitvoering van deze tutorial, vooral omdat de map-structuur niet vast is. Je wordt geacht dit zelf op te kunnen lossen, of anders deze tut niet te volgen.

Bewerkt: door marcootje
Link to comment
Delen op andere websites

Een reactie plaatsen

Je kan nu een reactie plaatsen en pas achteraf registreren. Als je al lid bent, log eerst in om met je eigen account een reactie te plaatsen.

Gast
Op dit onderwerp reageren...

×   Je hebt text geplaatst met opmaak.   Opmaak verwijderen

  Only 75 emoji are allowed.

×   Je link is automatisch ingevoegd.   In plaats daarvan weergeven als link

×   Je vorige bewerkingen zijn hersteld.   Alles verwijderen

×   You cannot paste images directly. Upload or insert images from URL.

  • Recent actief   0 leden

    • Er zijn hier geen geregistreerde gebruikers aanwezig.
×
×
  • Create New...