Deze website maakt gebruik van diensten van Google voor het tonen van advertenties en het bijhouden van bezoekersstatistieken. Google kan hiermee uw surfgedrag volgen. Zie voor meer informatie het privacybeleid van Google. Via Your Online Choices kunt u zich afmelden voor gepersonaliseerde advertenties. Deze melding verbergen.

15 Lokale bestanden

In hoofdstuk 3 hebben we gezien hoe we met include() PHP-code in een ander bestand kan worden ingevoegd in een script. En in hoofdstuk 10 zagen we hoe geüploade bestanden op de server kunnen worden opgeslagen. Dit hoofdstuk gaat dieper in op het werken met bestanden – die in beginsel geen PHP-code bevatten – op de server.

Theorie

In principe kan ieder bestand dat door de webserver kan worden benaderd door PHP worden gelezen, geschreven en verwijderd. Bestandsrechten kunnen er voor zorgen dat bepaalde bestanden niet of juist wel toegankelijk zijn. Omdat het instellen van bestandsrechten verschilt per besturingssysteem, gaat het te ver om dat hier te behandelen. Bedenk wel dat PHP niet om een bevestiging vraagt om iets te verwijderen of te overschrijven!

Bestanden lezen

Het principe van het lezen van bestanden in PHP komt er op neer dat de inhoud van een opgegeven bestand wordt ingelezen in een string-variabele. Vervolgens kunnen er desgewenst allerlei bewerkingen op deze variabele worden uitgevoerd. Zolang de bewerkingen niet expliciet worden opgeslagen, blijft het originele bestand onaangetast. Deze manier van werken met bestanden is vooral gericht op tekstbestanden. Het werkt ook met andere soorten bestanden, maar dan zijn de bewerkingsopties minimaal

De makkelijkste manier om de inhoud van een bestand in te lezen in een string is met behulp van de functie file_get_contents():

string file_get_contents ( string $filename [, bool $use_include_path = FALSE [, resource $context [, int $offset = 0 [, int $maxlen ]]]] )

Deze functie kent een aantal parameters, maar in de meeste gevallen is eigenlijk alleen de eerste parameter interessant: het pad naar het bestand dat moet worden ingelezen. Dit kan een relatief pad zijn ten opzichte van het script dat wordt uitgevoerd, een absoluut pad naar een bestand op de server, maar ook een URL.

<?php
$bestand_relatief
= file_get_contents('bestand.txt');
$bestand_absoluut = file_get_contents('/var/www/html/bestand.txt');
$website = file_get_contents('http://phpboek.net/phpboek-15-lokale_bestanden.php');
?>

Een tweede manier om de inhoud van een bestand te lezen is met behulp van de functies fopen(), fread() en fclose(). Het belangrijkste verschil is dat een lokaal bestand geopend met fopen() "gereserveerd" blijft totdat het script klaar is of totdat het bestand wordt vrijgegeven met fclose(). De drie functies werken als volgt:

resource fopen ( string $filename , string $mode [, bool $use_include_path = FALSE [, resource $context ]] )

Allereerst wordt met fopen() aangegeven welk bestand moet worden gelezen. Het is gebruikelijk om fopen() alleen bij lokale bestanden te gebruiken, maar ook hier mag een URL worden gebruikt. Als eerste parameter wordt de bestandsnaam opgegeven, net zoals bij file_get_contents(). Alleen is nu ook de tweede parameter verplicht. Deze geeft aan hoe het bestand moet worden geopend: om alleen te lezen, om alleen te schrijven, of allebei. Om een bestand alleen te lezen wordt de modus 'rb' gebruikt. Het resultaat van de functie moet worden toegewezen aan een (zelf te kiezen) variabele die dient als handvat voor fread() en fclose().

string fread ( resource $handle , int $length )

De tweede stap is de functie fread() om de daadwerkelijke inhoud van het bestand in te lezen in een string-variabele. Hierbij moet als eerste parameter het handvat worden opgegeven dat is gemaakt met fopen(). De tweede parameter geeft aan hoeveel bytes van het bestand moeten worden ingelezen. Alleen weten we vooraf meestal niet precies hoe groot een bestand is! Hiervoor kunnen we gebruik maken van de functie filesize(). Deze functie geeft de grootte van een bestand in bytes en vraagt alleen de locatie van het bestand als parameter. Dit is uiteraard dezelfde locatie als gebruikt bij fopen().

int filesize ( string $filename )
bool fclose ( resource $handle )

De laatste stap is om met behulp van fclose() het geopende bestand weer vrij te geven. Het bestand kan dan weer gebruikt worden door andere processen, ook als het script nog niet klaar is. fclose() vraagt als enige parameter het handvat dat gemaakt is met fopen(). Het is niet verplicht om een bestand vrij te geven. PHP doet dit dan automatisch wanneer het script klaar is. Toch is het goed om een bestand wel zelf te sluiten. Al is het maar om het inzicht in het werken met bestanden te vergroten.

In PHP-code zien de drie (of eigenlijk vier) functies er als volgt uit:

<?php
$handle
= fopen('bestand.txt', 'rb');
$inhoud = fread($handle, filesize('bestand.txt'));
fclose($handle);
?>

We kunnen dit script nog iets slimmer maken door 'bestand.txt' in een variabele te zetten. Dan hoeft deze bij wijzigen maar op één plaats te worden aangepast.

<?php
$bestand
= 'bestand.txt';
$handle = fopen($bestand, 'rb');
$inhoud = fread($handle, filesize($bestand));
fclose($handle);
?>

Bestanden schrijven

Het schrijven van een bestand gaat op bijna dezelfde manier als het lezen van een bestand via de fopen()-methode. Allereerst moet er een string-variabele zijn met de inhoud voor het te schrijven bestand. Daarna wordt het bestand geopend met fopen(), maar nu met de modus 'wb' om het bestand te schrijven. Deze modus zorgt er tevens voor dat een nieuw bestand wordt gemaakt als de opgegeven bestandslocatie nog niet bestaat. Vervolgens wordt de inhoud in het bestand geschreven met de functie fwrite(). Tot slot wordt het bestand gesloten met fclose().

int fwrite ( resource $handle , string $string [, int $length ] )

Net als fread() heeft fwrite() als eerste parameter het handvat nodig van het bestand dat is opgegeven met fopen(). Als tweede parameter wordt de te schrijven inhoud voor het bestand gegeven. De optionele derde parameter geeft aan hoeveel bytes van de string er naar het bestand geschreven moeten worden. Laat deze parameter weg om de volledige string naar het bestand te schrijven. In PHP-code ziet het geheel er als volgt uit:

<?php
$bestand
= 'bestand.txt';
$inhoud = 'Dit is de tekst die in het bestand wordt geschreven!';
$handle = fopen($bestand, 'wb');
$handle = fwrite($handle, $inhoud);
fclose($handle);
?>

Over mappen

Hoewel PHP mappen kan maken en verwijderen met de functies mkdir() en rmdir(), levert dit in de praktijk regelmatig problemen op. De meeste shared hosting omgevingen zijn dusdanig ingericht dat dit simpelweg niet mogelijk is. Daarom is het verstandig om niet zonder meer op deze functies te vertrouwen en de benodigde mappen handmatig aan te maken.

Bestanden verwijderen

Het verwijderen van bestanden via PHP is verrassend eenvoudig. Het is wel handig om te weten dat deze functie schuil gaat onder de ietwat cryptische naam unlink().

bool unlink ( string $filename [, resource $context ] )

Als eerste parameter wordt de bestandlocatie gegeven van het te verwijderen bestand. Het resultaat van de functie is TRUE wanneer het verwijderen gelukt is, of FALSE als het bestand om wat voor reden dan ook niet verwijderd kon worden. Let op! Er wordt niet om een bevestiging gevraagd en na uitvoeren van de functie is het bestand ook echt weg!

<?php
unlink
('bestand.txt');
?>

Bestandslijsten

PHP kan ook een overzicht maken van alle bestanden en mappen in een opgegeven map. Hiervoor wordt de functie scandir() gebruikt:

array scandir ( string $directory [, int $sorting_order = SCANDIR_SORT_ASCENDING [, resource $context ]] )

Als eerste verplichte parameter wordt de locatie van de map gegeven waarvan een overzicht moet worden gemaakt. Standaard wordt het overzicht op alfabetische volgorde gesorteerd. Om op omgekeerde alfabetische volgorde te sorteren kan als tweede parameter de waarde 1 of (vanaf PHP 5.4) SCANDIR_SORT_ASCENDING worden gegeven. Het resultaat van scandir() is een array met alle bestanden en mappen in de opgegeven map. Deze kunnen dan met behulp van bijvoorbeeld een foreach-lus in een lijst worden weergegeven:

<?php
$dir
= scandir('upload');
echo
'<ul>';
foreach (
$dir as $item) {
    echo
'<li>' . $item . '</li>';
}
echo
'</ul>';
?>

Merk op dat een maplijst altijd twee standaardvermeldingen bevat: '.' (een puntje) en '..' (twee puntjes). Deze staan voor respectievelijk 'deze map' en 'bovenliggende map', maar zijn niet zo mooi in een bestandslijst. Met behulp van een if uitdrukking kunnen we deze uit de lijst filteren:

<?php
$dir
= scandir('upload');
echo
'<ul>';
//loop door ieder item in map
foreach ($dir as $item) {
    
//toon . en .. niet in lijst
    
if (($item != '.') && ($item != '..')) {
        echo
'<li>' . $item . '</li>';
    }
}
echo
'</ul>';
?>

Mappen eerst

Vermeldingen van bestanden en mappen staan nu door elkaar in de lijst. Wat nu als we eerst alle mappen willen, en dan pas alle bestanden? Met behulp van de functie is_dir() kunnen we uitvinden of een gegeven vermelding een map is of niet.

bool is_dir ( string $filename )

Als eerste parameter wordt de bestandlocatie gegeven. Het resultaat van de functie is TRUE als dit een map is, of FALSE als (logischerwijs) een bestand is.

Door nu deze functie te combineren met het eerdere script kunnen de mappen en bestanden gescheiden worden. Het is dan nodig om drie lussen te gebruiken: een lus om de mappen en bestanden te scheiden naar aparte arrays en twee lussen om deze arrays in een lijst weer te geven. Let op dat bij is_dir() het juiste pad wordt gegeven. De naam van de map die wordt doorzocht moet er bij!

<?php
$dir
= scandir('upload');
$mappen = array();
$bestanden = array();
//loop door ieder item in map
foreach ($dir as $item) {
    
//bepaal of item een map is
    
if (is_dir('upload/'.$item)) {
        
$mappen[] = $item;
    }
    else {
        
$bestanden[] = $item;
    }
}
//maak lijst
echo '<ul>';
//eerst alle mappen
foreach ($mappen as $map) {
    
//toon . en .. niet in lijst
    
if (($map != '.') && ($map != '..')) {
        echo
'<li>' . $map . '</li>';
    }
}
//dan alle bestanden
foreach ($bestanden as $bestand) {
        echo
'<li>' . $bestand . '</li>';
}
echo
'</ul>';
?>

Praktijkvoorbeeld: Bestandslijst met submappen

In dit hoofdstuk hebben we gezien hoe we een bestandslijst kunnen maken en daarbij kunnen achterhalen of iets een map of bestand is. In hoofdstuk 14 hebben we gezien hoe we recursieve functies kunnen gebruiken om een lijst met subitems te maken. Door deze kennis samen te voegen kunnen we een bestandslijst maken waarbij PHP ook alles in de onderliggende mappen weergeeft. Dit doen we in vijf stappen. Dit hoofstuk heeft geen oefening, dus probeer het eerst zelf!

  1. Functie maken om de inhoud van een map weer te geven;
  2. . en .. uitfilteren;
  3. Onderscheid tussen mappen en bestanden;
  4. Functie recursief maken;
  5. Mappen eerst weergeven.

1. Functie maken

Het is van belang dat we een functie maken die de naam van de weer te geven map als parameter heeft. Hiermee kan later de functie recursief worden gemaakt door steeds naar de juiste map te verwijzen. Verder is deze functie niet heel veel meer dan het eenvoudige voorbeeld uit het theoriedeel, maar nu dus als functie. Om de code overzichtelijk te houden (en om het onszelf makkelijker te maken) gebruiken we nu echo direct in de functie.

<?php
//definieer de functie
function bestandslijst ($dir) {
    
//vind onderdelen in map
    
$lijst = scandir($dir);
    
//maak lijst
    
echo '<ul>';
    
//loop door ieder item in map
    
foreach ($lijst as $item) {
            echo
'<li>' . $item . '</li>';
    }
    echo
'</ul>';
}

//roep functie aan om lijst direct weer te geven
bestandslijst('upload');
?>

2. . en .. uitfilteren

Het is belangrijk om de vermeldingen . en .. uit te filteren. Doen we dat niet, dan gaat de recursieve functie alle bestanden op de machine tot in het oneindige herhalen!

<?php
//definieer de functie
function bestandslijst ($dir) {
    
//vind onderdelen in map
    
$lijst = scandir($dir);
    
//maak lijst
    
echo '<ul>';
    
//loop door ieder item in map
    
foreach ($lijst as $item) {
        
//. en .. uitfilteren
        
if (($item != '.') && ($item != '..')) {
            echo
'<li>' . $item . '</li>';
        }
    }
    echo
'</ul>';
}

//roep functie aan om lijst direct weer te geven
bestandslijst('upload');
?>

3. Onderscheid tussen mappen en bestanden

Omdat de recursieve functie alleen moet worden toegepast op mappen, moeten we mappen en bestanden uit elkaar halen. Bij is_dir plakken we parameter $dir voor de mapnaam, met een slash ertussen, om de juiste map te kunnen controleren. Als het een map is geven we de naam weer, ter verduidelijking met een '/' erachter en maken we alvast een sublijst aan.

<?php
//definieer de functie
function bestandslijst ($dir) {
    
//vind onderdelen in map
    
$lijst = scandir($dir);
    
//maak lijst
    
echo '<ul>';
    
//loop door ieder item in map
    
foreach ($lijst as $item) {
        
//. en .. uitfilteren
        
if (($item != '.') && ($item != '..')) {
            
//bepaal of item een map is
            
if (is_dir($dir.'/'.$item)) { //map
                
echo '<li>' . $item . '/';
                
                echo
'</li>';
            }
            else {
//bestand
                
echo '<li>' . $item . '</li>';
            }
        }
    }
    echo
'</ul>';
}

//roep functie aan om lijst direct weer te geven
bestandslijst('upload');
?>

4. Functie recursief maken

Nu is het tijd om ook de inhoud van submappen te doorlopen. De plek waar we de functie recursief gaan maken hebben we in de vorige stap eigenlijk al bepaald: onder de naam van de map, in het lijst-item <li>. Als parameter voor de recursieve functie gebruiken we dezelfde bestandslocatie als bij is_dir(). En dan gaat het eigenlijk al vanzelf goed!

<?php
//definieer de functie
function bestandslijst ($dir) {
    
//vind onderdelen in map
    
$lijst = scandir($dir);
    
//maak lijst
    
echo '<ul>';
    
//loop door ieder item in map
    
foreach ($lijst as $item) {
        
//. en .. uitfilteren
        
if (($item != '.') && ($item != '..')) {
            
//bepaal of item een map is
            
if (is_dir($dir.'/'.$item)) { //map
                
echo '<li>' . $item . '/';
                
//maak recursief, doorloop submap
                
bestandslijst($dir.'/'.$item);
                echo
'</li>';
            }
            else {
//bestand
                
echo '<li>' . $item . '</li>';
            }
        }
    }
    echo
'</ul>';
}

//roep functie aan om lijst direct weer te geven
bestandslijst('upload');
?>

5. Mappen eerst weergeven

Voor de overzichtelijkheid kunnen we op ieder niveau eerst de mappen (en eventuele inhoud) laten zien en daaronder de bestanden. Hiervoor moet de functie iets worden aangepast, door als iets een bestand is dit niet direct weer te geven, maar tijdelijk te bewaren in een hulp-array en de inhoud daarvan pas na het doorlopen van de map-inhoud weer te geven. De code voor de mappen laten we zoals deze is, door de bestanden als het ware 'op te sparen' komen de mappen vanzelf bovenaan.

<?php
//definieer de functie
function bestandslijst ($dir) {
    
//vind onderdelen in map
    
$lijst = scandir($dir);
    
//hulp-array voor bestanden
    
$bestanden = array();
    
//maak lijst
    
echo '<ul>';
    
//loop door ieder item in map en geef mappen direct weer
    
foreach ($lijst as $item) {
        
//. en .. uitfilteren
        
if (($item != '.') && ($item != '..')) {
            
//bepaal of item een map is
            
if (is_dir($dir.'/'.$item)) { //map
                
echo '<li>' . $item . '/';
                
//maak recursief, doorloop submap
                
bestandslijst($dir.'/'.$item);
                echo
'</li>';
            }
            else {
//bestand
                //sla bestand tijdelijk op in hulp-array
                
$bestanden[] = $item;
            }
        }
    }
    
//geef bestanden weer
    
foreach ($bestanden as $bestand) {
        echo
'<li>' . $bestand . '</li>';
    }
    echo
'</ul>';
}

//roep functie aan om lijst direct weer te geven
bestandslijst('upload');
?>

Zelftest

  1. Wat is de functie om een bestand te openen?
    1. file_get_contents()
    2. fopen()
    3. fread()
    4. scandir()
  2. Wat is de functie om een bestand te schrijven?
    1. fopen()
    2. file_set_contents()
    3. fwrite()
    4. filesize()
  3. Wat is de functie om een bestand te verwijderen?
    1. unlink()
    2. unset()
    3. delete()
    4. remove()
  4. Welke van onderstaande stellingen is/zijn waar?
    I             scandir() kan in één keer alle submappen lezen;
    II            Het is verplicht om fclose() te gebruiken.
    1. Beide stellingen zijn onwaar.
    2. Alleen stelling I is waar.
    3. Alleen stelling II is waar.
    4. Beide stellingen zijn waar.
  5. Welke van onderstaande stellingen is/zijn waar?
    I             PHP vraagt om een bevestiging bij het verwijderen van een bestand;
    II            is_dir() kan gebruikt worden om te bepalen of iets een bestand is.
    1. Beide stellingen zijn onwaar.
    2. Alleen stelling I is waar.
    3. Alleen stelling II is waar.
    4. Beide stellingen zijn waar.
  6. Welke van onderstaande stellingen is/zijn waar?
    I             PHP kan geen mappen aanmaken;
    II            sort() moet worden gebruikt om het resultaat van scandir() op alfabetische volgorde te plaatsen.
    1. Beide stellingen zijn onwaar.
    2. Alleen stelling I is waar.
    3. Alleen stelling II is waar.
    4. Beide stellingen zijn waar.

Antwoorden zelftest

Antwoorden

  1. b
  2. c
  3. a
  4. a
  5. c
  6. a