David Steinsland – informatikkstudent og webutvikler

Innlegg fra kategorien «Programmering»

Cache PHP-sider: kort og enkelt

| Ingen kommentarer »

Når man skriver nettsider med PHP er det mye som kan være med på å dra opp lastetiden. For ikke å snakke om alle CSS- og JavaScript-filer som også må lastes ned. Hva kan man så gjøre?

Om du jobber med databaser kan du for eksempel skru på MySQL Query Cacher, samtidig som du kan mellomlagre resultatet i HTML-, JSON eller XML-format. Men alt dette krever igrunn litt arbeid, samt at noen koder her og der må endres.

$lastModified = filemtime (__FILE__);
$etagFile = md5_file (__FILE__);
$ifModifiedSince = isset ($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : FALSE;
$etagHeader = isset ($_SERVER['HTTP_IF_NONE_MATCH'] ? trim ($_SERVER['HTTP_IF_NONE_MATCH']) : FALSE;

header("Last-Modified: " . gmdate("D, d M Y H:i:s", $lastModified) . " GMT");
header("Etag: $etagFile");
header('Cache-Control: public');

//check if page has changed. If not, send 304 and exit
if (@strtotime ($ifModifiedSince) == $lastModified || $etagHeader == $etagFile)
{
       header ("HTTP/1.1 304 Not Modified");
       exit;
}

//your normal code below

Om du limer inn koden ovenfor i starten på de PHP-filene du ønsker å mellomlagre, merker du forskjellen med én gang. Jeg har selv testet koden i flere prosjekter, og den fungerer utmerket. Det som er verdt å merke seg, er at den ikke fanger opp endringer i dynamisk innhold med én gang.
Den merker så klart endringer på seg selv, men dersom du henter innhold fra en database så kan det ta noen minutter før det vises.

Denne prosessen kan, så vidt jeg vet, ikke fremskyndes siden mellomlageret ligger i nettleseren, og det er ikke mulig å fjerne det (dette må sluttbruker gjøre manuelt).

Oppgraderingsdag

| Ingen kommentarer »

I disse «sene» juledager har jeg fått oppgradert serveren min, deriblant PHP, MySQL og Apache til aller siste versjon. Mest hyggelig er det jo med en etterlengtet MySQL-oppgradering, som såvidt har blitt oppdatert Oracle kjøpte dem opp.

Anbefaler også at du gjør det samme, og da spesielt MySQL 5.5-oppgraderingen hvor InnoDB endelig er satt som standard og som kan skiltes som 540 % mer effektiv (Windows)!

Så nå kjører jeg altså PHP 5.3.4, MySQL 5.5 og Apache 2.2.17, på en Windows 7-maskin, som tok meg i underkant av 15 minutter å gjøre.

PS:

MySQL Query Cache

| Ingen kommentarer »

Når du bygger applikasjoner for nettet, vil du komme til et punkt hvor enkelte komponenter kan dra ned ytelsen eller lastetiden. Dette kan for eksempel være krevende PHP-kode som store løkker, behandling av store mengder data, eller krevende MySQL-spørringer.

Du tenker da kanskje å implementere et mellomlager. Dette kan du gjøre på applikasjonsnivå og på servernivå.

La oss si at du har bygget deg en RSS-leser, som henter XML-data fra nyhetsstrømmer fra nettet. Over tid vil serveren bruke mye ressurser på å laste disse ned fra nettet hele tiden, og som mottiltak kan du lagre XML-dataene på din egen server (som blir oppdatert jevnlig). På den måten har du implementert et mellomlager på applikasjonsnivå, hvor du lagrer data i en gitt periode før du oppdaterer mellomlageret med ny data (for eksempel hver 30. minutt).

Du kan også implementere løsninger på servernivå, som kan bedre ytelsen til PHP ved at den kompilerer koden og lagrer den på RAM. Her har du eksempler som PHP-Accelerator, eAccelerator og Zend Optimizer.

Men hva når det kommer til MySQL-spørringer?

Her kan du også implementere et mellomlager på applikasjonsnivå, men da må du sette opp dine egne metoder som kjører spørringer etc. Du følger da samme tankegangen som med RSS-leseren:

  • Når spørringen blir kjørt for første gang, lagrer du resultatet som spørringen gir i en tekstfil.
  • Neste gang spørringen blir kjørt, returnerer du dataene fra tekstfilen istedenfor å kjøre spørringen på nytt.

Men dette kan være uønskelig i flere tilfeller:

  • du må sette opp egne metoder, da PHPs innebygde MySQL-bibliotek ikke støtter caching
  • dataene kan være unøyaktige, og du bør hele tiden sørge for at tekstfilene inneholder oppdatert informasjon

Typisk implementering ville vært noe slik:

class MySQL
{
	public function query ($query)
	{
		$cache = 'cache/' . md5 ($query) . '.mysql';

		if ( file_exists ($cache))
		{
			$data = unserialize (file_get_contents ($cache));
		}
		else
		{
			$query = mysql_query ($query);

			$data = array ();

			while ($row = mysql_fetch_assoc ($data))
			{
				$data[] = $row;
			}

			file_put_contents ($cache, $data);
		}

		return $data;
	}
}

$mysql = new MySQL ('localhost', 'user', 'pw', 'db');

$data = $mysql->query ('SELECT foo FROM bar');

MySQL Query Cache

Men en bedre løsning vil være å dra nytte av MySQLs egen Query Cacher. Denne returnerer mellomlagret data dersom det er forblitt uforandret, og oppdaterer så snart det er ny data. I tillegg blir spørringene utført på tilnærmet 0 sekunder. Altså en veldig grei sak, dette her!

I de fleste tilfeller er Cache-muligheten skrudd av som standard, og du kan sjekke dette ved å kjøre følgende kommando til MySQL:

mysql> show variables like 'query%';
+------------------------------+---------+
| Variable_name                | Value   |
+------------------------------+---------+
| query_alloc_block_size       | 8192    |
| query_cache_limit            | 1048576 |
| query_cache_min_res_unit     | 4096    |
| query_cache_size             | 0       |
| query_cache_type             | ON      |
| query_cache_wlock_invalidate | OFF     |
| query_prealloc_size          | 8192    |
+------------------------------+---------+

Her ser vi at query_cache_size er satt til 0, noe som fører til at Cache-funksjonaliteten er skrudd av. For å aktivere denne, kjør følgende kommando:

mysql> set global query_cache_size=50000000;

Det er i bunn og grunn alt du trenger å gjøre! Og dette fungerer flott som fjell.

Lykke til!

ActionScript 3 og nøkkelordet «with»

| 2 kommentarer »

Nylig kom jeg over nøkkelordet «with» i ActionScript 3. Etter å ha programmert AS3.0 en liten stund, har jeg aldri kommet over bruken av nøkkelordet til nå. Første gangen jeg så bruken av det, var i en kildekode jeg fant ved en tilfeldighet på Sniplr.com.

Det kan bli brukt til å skrive renere kode når du skal sette flere verdier til samme objekt, som for eksempel i en slik situasjon:

var printer:PrintJob = new PrintJob();

if (printer.start())
{
    printer.addPage (content_mc);
    printer.send();
}

Med «with» blir det:

with (new PrintJob())
{
	if (start()) {
		addPage (content_mc);
		send();
	}
}

Lekkert?

RecursiveIterator med egne filtre

| Ingen kommentarer »

Er det noe jeg virkelig elsker med PHP, må det være Standard PHP Library, eller SPL som det også heter. Det er en samling av innebygde klasser som gjør deg istand til for eksempel å kjøre (iterere) gjennom arrays, filer, mappestrukturer (endimensjonalt) eller flerdimensjonalt (rekursiv). Alt dette ved en solid OOP-struktur!

Scenario: du skal iterere rekursivt gjennom en mappestruktur, og ønsker kun å hente ut filer med endelsen «txt».

Hva gjør du? Du kunne brukt en kombinasjon av scandir() og din egen rekursive funksjon, eller glob(), eller, eller…

Hva dersom du ønsker å bruke kodene senere, flere ganger, bare med små endringer? Det er virkelig en grense for hvor fleksibelt et system kan være når det er satt til å utføre én bestemt oppgave. Utnytter du OOP, kan du snu på dette.

I koden nedenfor henter jeg ut alle filer (uansett nivå) som befinner seg inni mappen filer/.

foreach (new RecursiveIteratorIterator(
	new RecursiveDirectoryIterator('filer')
) as $file
)
{
    echo $file->getFilename();
}

Men skulle ikke jeg filtrere bort enkelte filer? Hvordan gjør jeg det?

Det vi trenger å gjøre, er å lage en klasse i PHP som arver egenskaper fra FilterIterator. Den må fyre i gang RecursiveDirectoryIterator og RecursiveIteratorIterator, i tillegg må den ha metoden accept().

Her har jeg skrevet enda et enkelt eksempel, hvor jeg kun henter ut filer med endelsen «txt».

class RecursiveFilter extends FilterIterator
{
	protected $_extensions = array ();

	public function __construct ($path, array $extension = array ())
	{
		$this->_extensions = $extensions;

		parent::__construct(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)));
	}

	/**
	 * Bestemmer hvilke filer som skal bli godtatt
	 * Metoden må returnere FALSE eller TRUE, alt ettersom
	 * om filen er godtatt eller ei.
	 */
	public function accept ()
	{

		// get the file that are being iterated
		$item = $this->getInnerIterator ();

		return in_array ( pathinfo ($item->getBasename(), PATHINFO_EXTENSION), $this->_extensions);
	}
}

$iterator = new RecursiveFilter ('filer', array ('txt'));

foreach ($iterator as $item)
{
	echo $item->getBasename();
}

Så enkelt kan det gjøres. I teorien har vi igrunn fjernet sjekken ut av foreach-løkken, og inn i egen klasse. Det virker unødvendig, men med tanke på gjenbruk og abstraksjon så er dette en meget god idé.

Om du ønsker alle filer som ikke har endelsen «txt», så snur du bare på sjekken som utføres i accept() til:

return ! in_array ( pathinfo ($item->getBasename(), PATHINFO_EXTENSION), $this->_extensions);

Du kan selvfølgelig utvide accept() til så mye du vil, og ta høyde for alt du ønsker. $item inneholder et FileInfo-objekt, så da kan du bare slå deg løs!