10. PHP – operacje na strukturze systemu plików


Dołączanie plików

Jeżeli nasz skrypt zawiera dużo kodu możemy go podzielić na mniejsze partie, a później „sklejać” w całość. Po co? Zobaczmy.

  • Lepsze zarządzanie projektem
  • Wielokrotne użycie kodu (DRY – Don’t Repeat Yourself)
  • Łatwiejsze utrzymanie i aktualizacja
  • Separacja odpowiedzialności
  • Współpraca w zespole
dołączanie pliku

Wiemy już jaki jest sens dzielenia pliku, ale jak to później połączyć? ano tak:

include()

dołącza plik do kodu, w razie braku pliku generuje ostrzeżenie kontynuując pracę skryptu

include('header.php');
include('functions.php');

// Skrypt działa dalej, nawet jeśli pliku nie ma
echo "Strona działa";

Kiedy używać:

  • Pliki opcjonalne (np. moduły, które mogą nie istnieć)
  • Elementy strony, których brak nie blokuje całości

include_once()

dołącza plik do kodu jednokrotnie, w razie braku pliku generuje ostrzeżenie kontynuując pracę skryptu

include_once('config.php');
include_once('config.php');  // Zostanie zignorowane

Po co:

  • Zapobiega wielokrotnemu dołączeniu tego samego pliku
  • Unika redefinicji funkcji i klas

require()

dołącza plik do kodu, w razie braku pliku generuje błąd i przerywa działanie skryptu

require('config.php');
require('database.php');

// Jeśli któryś plik nie istnieje, skrypt się zatrzyma
echo "Ta linia może się nie wykonać";

Kiedy używać:

  • Pliki krytyczne (konfiguracja, baza danych)
  • Elementy bez których aplikacja nie może działać

require_once()

dołącza plik do kodu jednokrotnie, w razie braku pliku generuje błąd i przerywa działanie skryptu

require_once('config.php');
require_once('config.php');  // Zostanie zignorowane

Najczęściej używane – łączy bezpieczeństwo require z ochroną przed wielokrotnym dołączeniem.

Porównanie funkcji

Funkcja Przy braku pliku Wielokrotne dołączenie
include Warning, kontynuuje ✓ Dozwolone
include_once Warning, kontynuuje ✗ Tylko raz
require Fatal Error, przerywa ✓ Dozwolone
require_once Fatal Error, przerywa ✗ Tylko raz

Składnia ścieżek

// Ścieżka względna - od bieżącego pliku
require_once('header.php');
require_once('./includes/config.php');
require_once('../parent-dir/file.php');

// Ścieżka bezwzględna - od roota serwera
require_once('/var/www/html/config.php');

// Ze zmienną __DIR__ (zalecane)
require_once(__DIR__ . '/includes/config.php');

// Przykład
$base = __DIR__;
require_once($base . '/config/database.php');
require_once($base . '/includes/functions.php');

Używaj __DIR__ dla pewności, że ścieżka jest poprawna niezależnie od miejsca wywołania.

Modularyzacja kodu

Przykład struktury projektu

projekt/
├── index.php
├── config/
│   ├── config.php
│   └── database.php
├── includes/
│   ├── header.php
│   ├── footer.php
│   └── menu.php
├── functions/
│   ├── auth.php
│   └── helpers.php
└── assets/
    ├── css/
    ├── js/
    └── images/

 

Nasze pliki wyglądają teraz tak:

header.php

<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $pageTitle ?? 'Moja Strona' ?></title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<header>
<h1>Moja Strona WWW</h1>
</header>

menu.php

<nav id="menu">
<ul>
<li><a href="index.php">Start</a></li>
<li><a href="about.php">O nas</a></li>
<li><a href="contact.php">Kontakt</a></li>
<li><a href="blog.php">Blog</a></li>
</ul>
</nav>

footer.php

    <footer>
<p>&copy; <?= date('Y') ?> Moja Strona. Wszelkie prawa zastrzeżone.</p>
</footer>

<script src="assets/js/main.js"></script>
</body>
</html>

index.php

<?php
// Konfiguracja
require_once(__DIR__ . '/config/config.php');

// Ustawienia strony
$pageTitle = 'Strona główna';

// Dołącz nagłówek i menu
require_once(__DIR__ . '/includes/header.php');
require_once(__DIR__ . '/includes/menu.php');
?>

<main>
<section>
<h2>Witamy na stronie głównej</h2>
<p>To jest treść strony głównej.</p>
</section>
</main>

<?php
// Dołącz stopkę
require_once(__DIR__ . '/includes/footer.php');
?>

config.php

<?php
// Konfiguracja aplikacji
define('SITE_NAME', 'Moja Strona');
define('SITE_URL', 'https://example.com');
define('SITE_EMAIL', 'kontakt@example.com');

// Ustawienia bazy danych
define('DB_HOST', 'localhost');
define('DB_NAME', 'mojabaza');
define('DB_USER', 'root');
define('DB_PASS', '');

// Strefa czasowa
date_default_timezone_set('Europe/Warsaw');

// Raportowanie błędów (development)
error_reporting(E_ALL);
ini_set('display_errors', 1);

Użycie:

<?php
require_once(__DIR__ . '/includes/Template.php');

$template = new Template();
$template->setTitle('Strona główna');
$template->addStyle('assets/css/style.css');
$template->addScript('assets/js/main.js');

$template->header();
?>

<h1>Treść strony</h1>

<?php
$template->footer();
?>

Operacje na katalogach

is_dir() / is_file()

Sprawdzają, czy ścieżka wskazuje na katalog lub plik.

$path = 'uploads';

if (is_dir($path)) {
    echo "$path jest katalogiem";
}

if (is_file('config.php')) {
    echo "config.php jest plikiem";
}

// Sprawdź przed operacją
if (is_dir('logs')) {
    echo "Katalog logs istnieje";
} else {
    mkdir('logs');
    echo "Katalog logs został utworzony";
}

getcwd() / chdir()

Pobieranie i zmiana bieżącego katalogu roboczego.

// Bieżący katalog
echo getcwd(); // np. /var/www/html

// Zmień katalog
chdir('public_html');
echo getcwd(); // /var/www/html/public_html

// Powrót do poprzedniego
chdir('..');
echo getcwd(); // /var/www/html

scandir()

Wypisze wszystkie pliki i katalogi z określonej lokalizacji. Pierwszy parametr to lokalizacja katalogu, drugi to sortowanie: 0 (rosnąco – domyślny) lub 1(malejąco)

$dir = 'uploads';
$files = scandir($dir);

print_r($files);
/*
Array
(
[0] => .
[1] => ..
[2] => image1.jpg
[3] => document.pdf
[4] => subfolder
)
*/

// Sortowanie malejąco
$files = scandir($dir, SCANDIR_SORT_DESCENDING);

// Pomiń . i ..
$files = array_diff(scandir($dir), ['.', '..']);

// Wyświetl tylko pliki
foreach ($files as $file) {
$path = $dir . '/' . $file;
if (is_file($path)) {
echo "Plik: $file\n";
}
}

mkdir() i rmdir()

Tworzenie katalogu:

Przykład:

// Prosty katalog
if (mkdir('uploads')) {
echo "Katalog utworzony";
}

// Z prawami dostępu (Unix/Linux)
mkdir('cache', 0755);

// Rekurencyjne tworzenie (zagnieżdżone)
mkdir('a/b/c/d', 0755, true);

// Sprawdź czy istnieje przed utworzeniem
if (!is_dir('logs')) {
mkdir('logs', 0755, true);
}

Z parametrami (np. prawa dostępu i tworzenie zagnieżdżonych katalogów):

mkdir('a/b/c', 0777, true);  // true → rekurencyjnie tworzy strukturę

Prawa dostępu (Unix/Linux):

  • 0755 – właściciel: rwx, grupa: r-x, inni: r-x
  • 0777 – wszyscy: rwx (rzadko zalecane ze względów bezpieczeństwa)
  • 0700 – tylko właściciel: rwx

Usuwanie katalogu:

Przykład:

// Usuń pusty katalog
if (rmdir('temp')) {
echo "Katalog usunięty";
}

// rmdir() działa TYLKO dla pustych katalogów!

// Usuń katalog z zawartością (rekurencyjnie)
function rmdirRecursive($dir) {
if (!is_dir($dir)) {
return false;
}

$files = array_diff(scandir($dir), ['.', '..']);

foreach ($files as $file) {
$path = $dir . '/' . $file;
is_dir($path) ? rmdirRecursive($path) : unlink($path);
}

return rmdir($dir);
}

// Użycie
rmdirRecursive('old_folder');

rmdir() działa tylko, jeśli katalog jest pusty.

Funkcje ścieżek — basename(), dirname(), pathinfo(), realpath()

Służą do analizowania ścieżek bez potrzeby operowania na stringach ręcznie.

basename() — zwraca nazwę pliku

$path = '/var/www/html/index.php';

echo basename($path);  // index.php

// Bez rozszerzenia
echo basename($path, '.php');  // index

dirname() — zwraca katalog nadrzędny

$path = '/var/www/html/index.php';

echo dirname($path);  // /var/www/html

// Poziom wyżej
echo dirname($path, 2);  // /var/www (PHP 7.0+)

pathinfo() — zwraca tablicę z częściami ścieżki

$path = '/var/www/html/images/photo.jpg';

$info = pathinfo($path);
print_r($info);
/*
Array
(
    [dirname] => /var/www/html/images
    [basename] => photo.jpg
    [extension] => jpg
    [filename] => photo
)
*/

// Konkretna część
echo pathinfo($path, PATHINFO_EXTENSION);  // jpg
echo pathinfo($path, PATHINFO_FILENAME);   // photo
echo pathinfo($path, PATHINFO_DIRNAME);    // /var/www/html/images
echo pathinfo($path, PATHINFO_BASENAME);   // photo.jpg

realpath() – bezwzględna ścieżka

// Bieżący katalog: /var/www/html

echo realpath('.');           // /var/www/html
echo realpath('..');          // /var/www
echo realpath('./config.php'); // /var/www/html/config.php

// Sprawdź czy plik istnieje
if (realpath('config.php') !== false) {
    echo "Plik istnieje";
}

Nowoczesne stałe ścieżek (PHP 5.3+)

// __DIR__ - katalog bieżącego pliku
echo __DIR__;  // /var/www/html

// __FILE__ - pełna ścieżka bieżącego pliku
echo __FILE__;  // /var/www/html/index.php

// Zastosowanie
require_once(__DIR__ . '/config/database.php');
require_once(__DIR__ . '/../includes/functions.php');

// Bezpieczne budowanie ścieżek
$configPath = __DIR__ . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'app.php';

Otwieranie katalogów — opendir(), readdir(), closedir()

Służy do przeglądania zawartości katalogów, np. listowania plików.

$dir = 'uploads';

if ($handle = opendir($dir)) {
    echo "Zawartość katalogu $dir:<br>";
    
    while (($file = readdir($handle)) !== false) {
        // Pomiń . i ..
        if ($file != '.' && $file != '..') {
            $type = is_dir("$dir/$file") ? 'katalog' : 'plik';
            echo "• $file ($type)<br>";
        }
    }
    
    closedir($handle);
}

Nowoczesna alternatywa – scandir():

$dir = 'uploads';
$files = array_diff(scandir($dir), ['.', '..']);

foreach ($files as $file) {
    $path = "$dir/$file";
    $type = is_dir($path) ? 'katalog' : 'plik';
    echo "• $file ($type)<br>";
}

Podsumowanie

OperacjaFunkcjaUwagi
Blokowanie plikuflock()Zapobiega konfliktom przy zapisie
Kopiowaniecopy()Nadpisuje, jeśli plik istnieje
Przenoszenie / zmiana nazwyrename()Działa dla plików i katalogów
Usuwanie plikuunlink()Bez kosza
Tworzenie katalogumkdir()Obsługuje tryby i zagnieżdżenie
Usuwanie katalogurmdir()Katalog musi być pusty
Analiza ścieżekbasename(), dirname(), pathinfo()Bardzo pomocne przy pracy z uploadem
Przeglądanie katalogówopendir(), readdir(), closedir()Iteracja po plikach

Nowoczesne podejście

SPL DirectoryIterator (PHP 5.0+)

$dir = 'uploads';

foreach (new DirectoryIterator($dir) as $file) {
    if ($file->isDot()) continue;  // pomiń . i ..
    
    echo $file->getFilename();
    
    if ($file->isDir()) {
        echo " [katalog]";
    } elseif ($file->isFile()) {
        echo " [plik, " . $file->getSize() . " bajtów]";
    }
    
    echo "\n";
}

RecursiveDirectoryIterator – przeglądanie rekurencyjne

$dir = 'project';

$iterator = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($dir),
    RecursiveIteratorIterator::SELF_FIRST
);

foreach ($iterator as $file) {
    if ($file->isFile()) {
        echo $file->getPathname() . "\n";
    }
}

FilesystemIterator – więcej opcji

$dir = 'uploads';

$iterator = new FilesystemIterator($dir, 
    FilesystemIterator::SKIP_DOTS  // pomiń . i ..
);

foreach ($iterator as $file) {
    echo $file->getFilename() . "\n";
}

Glob – wyszukiwanie wzorców

// Wszystkie pliki PHP
$phpFiles = glob('*.php');
print_r($phpFiles);

// Wszystkie obrazy
$images = glob('images/*.{jpg,jpeg,png,gif}', GLOB_BRACE);

// Rekurencyjne wyszukiwanie
$allPhp = glob('**/*.php', GLOB_BRACE);

// Przykład: usuń wszystkie pliki tymczasowe
foreach (glob('temp/*.tmp') as $file) {
    unlink($file);
}

Tabela funkcji – szybka ściąga

 

OperacjaFunkcjaOpis
Dołączanie  
 include()Dołącz, warning przy błędzie
 include_once()Dołącz raz, warning
 require()Dołącz, fatal error przy błędzie
 require_once()Dołącz raz, fatal error
Katalogi  
 is_dir()Czy to katalog?
 mkdir()Utwórz katalog
 rmdir()Usuń pusty katalog
 scandir()Lista plików/katalogów
 getcwd()Bieżący katalog
 chdir()Zmień katalog
Ścieżki  
 basename()Nazwa pliku
 dirname()Katalog nadrzędny
 pathinfo()Szczegóły ścieżki
 realpath()Bezwzględna ścieżka

Dobre praktyki

1. Zawsze używaj require_once dla plików krytycznych

// ŹLE
include('config.php');

// DOBRZE
require_once(__DIR__ . '/config/config.php');

2. Używaj DIR zamiast względnych ścieżek

// ŹLE - może nie działać
require_once('../includes/header.php');

// DOBRZE - zawsze działa
require_once(__DIR__ . '/../includes/header.php');

3. Sprawdzaj istnienie katalogów przed operacjami

// ŹLE
mkdir('cache');

// DOBRZE
if (!is_dir('cache')) {
    mkdir('cache', 0755, true);
}

4. Używaj separatora katalogów

// ŹLE - nie działa na Windows
$path = '/var/www/html/config.php';

// DOBRZE - uniwersalne
$path = __DIR__ . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'config.php';

// Lub po prostu /
$path = __DIR__ . '/config/config.php';  // działa na wszystkich systemach

5. Nigdy nie ufaj danym użytkownika w ścieżkach

// ŹLE - podatne na Path Traversal
$file = $_GET['file'];
include("pages/$file.php");  // Użytkownik może wpisać: ../../config/database

// DOBRZE - walidacja
$file = basename($_GET['file']);  // usuwa ścieżki
$allowed = ['home', 'about', 'contact'];

if (in_array($file, $allowed)) {
    include("pages/$file.php");
}

Przykłady praktyczne

1. Prosty system szablonów

functions/template.php:

<?php
function renderTemplate(string $templateName, array $data = []): void {
    $templatePath = __DIR__ . '/../templates/' . $templateName . '.php';
    
    if (!file_exists($templatePath)) {
        throw new Exception("Template not found: $templateName");
    }
    
    // Wyciągnij zmienne z tablicy
    extract($data);
    
    // Dołącz szablon
    require($templatePath);
}

templates/page.php:

<!DOCTYPE html>
<html lang="pl">
<head>
    <title><?= htmlspecialchars($title ?? 'Strona') ?></title>
</head>
<body>
    <h1><?= htmlspecialchars($heading) ?></h1>
    <p><?= htmlspecialchars($content) ?></p>
</body>
</html>

index.php:

<?php
require_once(__DIR__ . '/functions/template.php');

renderTemplate('page', [
    'title' => 'Moja Strona',
    'heading' => 'Witamy',
    'content' => 'To jest treść strony.'
]);

2. Automatyczne ładowanie plików (Autoloader)

<?php
// autoload.php

spl_autoload_register(function($className) {
    $baseDir = __DIR__ . '/classes/';
    $file = $baseDir . str_replace('\\', '/', $className) . '.php';
    
    if (file_exists($file)) {
        require_once($file);
    }
});

// Użycie
require_once('autoload.php');

$user = new User();  // automatycznie załaduje classes/User.php
$db = new Database(); // automatycznie załaduje classes/Database.php

3. Galeria zdjęć z katalogu

<?php
function displayGallery(string $dir): void {
    $extensions = ['jpg', 'jpeg', 'png', 'gif'];
    $images = [];
    
    foreach ($extensions as $ext) {
        $images = array_merge($images, glob("$dir/*.$ext"));
    }
    
    if (empty($images)) {
        echo "<p>Brak zdjęć w galerii.</p>";
        return;
    }
    
    echo '<div class="gallery">';
    foreach ($images as $image) {
        $filename = basename($image);
        echo '<div class="gallery-item">';
        echo "<img src='$image' alt='$filename' loading='lazy'>";
        echo "<p>$filename</p>";
        echo '</div>';
    }
    echo '</div>';
}

// Użycie
displayGallery('images/gallery');
?>

<style>
.gallery {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 1rem;
}

.gallery-item img {
    width: 100%;
    height: 200px;
    object-fit: cover;
}
</style>

4. Lista plików do pobrania

<?php
function listDownloads(string $dir): void {
    if (!is_dir($dir)) {
        echo "Katalog nie istnieje.";
        return;
    }
    
    $files = array_diff(scandir($dir), ['.', '..']);
    
    if (empty($files)) {
        echo "<p>Brak plików do pobrania.</p>";
        return;
    }
    
    echo '<ul class="downloads">';
    foreach ($files as $file) {
        $path = "$dir/$file";
        
        if (is_file($path)) {
            $size = filesize($path);
            $sizeFormatted = formatBytes($size);
            $extension = pathinfo($path, PATHINFO_EXTENSION);
            
            echo '<li>';
            echo "<a href='$path' download>";
            echo "<strong>$file</strong> ($sizeFormatted, .$extension)";
            echo '</a>';
            echo '</li>';
        }
    }
    echo '</ul>';
}

function formatBytes(int $bytes, int $precision = 2): string {
    $units = ['B', 'KB', 'MB', 'GB', 'TB'];
    
    for ($i = 0; $bytes > 1024; $i++) {
        $bytes /= 1024;
    }
    
    return round($bytes, $precision) . ' ' . $units[$i];
}

// Użycie
listDownloads('downloads');

5. Cache systemu

<?php
class FileCache {
    private string $cacheDir;
    private int $ttl;  // Time To Live w sekundach
    
    public function __construct(string $cacheDir = 'cache', int $ttl = 3600) {
        $this->cacheDir = $cacheDir;
        $this->ttl = $ttl;
        
        if (!is_dir($cacheDir)) {
            mkdir($cacheDir, 0755, true);
        }
    }
    
    public function get(string $key): mixed {
        $file = $this->getCacheFile($key);
        
        if (!file_exists($file)) {
            return null;
        }
        
        // Sprawdź czy nie wygasł
        if (time() - filemtime($file) > $this->ttl) {
            unlink($file);
            return null;
        }
        
        $content = file_get_contents($file);
        return unserialize($content);
    }
    
    public function set(string $key, mixed $value): bool {
        $file = $this->getCacheFile($key);
        $content = serialize($value);
        return file_put_contents($file, $content) !== false;
    }
    
    public function delete(string $key): bool {
        $file = $this->getCacheFile($key);
        
        if (file_exists($file)) {
            return unlink($file);
        }
        
        return false;
    }
    
    public function clear(): void {
        $files = glob($this->cacheDir . '/*');
        
        foreach ($files as $file) {
            if (is_file($file)) {
                unlink($file);
            }
        }
    }
    
    private function getCacheFile(string $key): string {
        return $this->cacheDir . '/' . md5($key) . '.cache';
    }
}

// Użycie
$cache = new FileCache('cache', 3600);  // 1 godzina TTL

// Zapisz
$cache->set('users', ['Jan', 'Anna', 'Piotr']);

// Odczytaj
$users = $cache->get('users');

// Usuń
$cache->delete('users');

// Wyczyść wszystko
$cache->clear();

Zadanie dla klas, które miały HTMLa:

Rozpocznij tworzenie autorskiego CMS – możesz wykorzystać do tego stronę www, którą posiadasz (nie WordPress, Joomla itp.).

Wydziel z niej bloki odpowiedzialne za formatowanie tych części strony, które się nie zmieniają (jak w przykładzie wyżej) tzn.:

  • nagłówek
  • menu
  • stopka
  • część główna strony
  • panele boczne
  • itp.