Szybki quiz – przypomnienie poprzedniej lekcji
1. Co zostanie wyświetlone?
$zadanie = ['tytul' => 'PHP', 'xp' => 20];
echo count($zadanie) . " " . $zadanie['xp'];
2. Znajdź błąd. Co zobaczysz po uruchomieniu?
$zadania = [
['tytul' => 'A', 'xp' => 10],
['tytul' => 'B', 'xp' => 5]
];
foreach ($zadania as $zadanie) {
echo $zadania['tytul'];
}
3. Jaki będzie wynik?
$terminy = ['2026-10-02', '2026-09-20'];
usort($terminy, function ($a, $b) {
return $a <=> $b;
});
echo $terminy[0];
A co wyświetli ten sam kod, jeśli daty zapiszemy jako '02.10.2026' i '20.09.2026'? Czy wynik będzie poprawny?
4. Dlaczego ten kod nie wypisze posortowanej listy?
$posortowane = usort($zadania, function ($zadanieA, $zadanieB) {
return $zadanieA['termin'] <=> $zadanieB['termin'];
});
print_r($posortowane);
5. Uzupełnij, aby funkcja zwracała sumę XP wszystkich zadań:
return array_sum(array_column($zadania, ______));
6. Co zwróci sumujXp([]) w wersji z pętlą foreach z Lekcji 9? Dlaczego nie ma błędu?
Odpowiedzi
2 20— tablica ma dwa elementy (kluczetytulixp).- W pętli użyto
$zadania(cała lista) zamiast$zadanie(jedno zadanie). Lista nie ma klucza'tytul', więc dla każdego obiegu pojawi się ostrzeżenie o niezdefiniowanym kluczu i nic nie zostanie wypisane. Poprawnie:echo $zadanie['tytul'];. 2026-09-20. Przy zapisie'02.10.2026'i'20.09.2026'na początku będzie'02.10.2026'— błędnie, bo teksty są porównywane znak po znaku, a'0'jest „mniejsze” niż'2'. FormatRRRR-MM-DDtego problemu nie ma.usort()zmienia tablicę, na której pracuje, i nie zwraca nowej tablicy (zwracatrue).print_r($posortowane)wypisze1. Poprawnie: wywołaćusort($zadania, ...), a potemprint_r($zadania)— albo użyć funkcjisortujPoTerminie(), która zwraca posortowaną kopię.'xp'0— pętla nie wykona się ani razu, więc funkcja zwraca początkową wartość$suma = 0.
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
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>© <?= 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-x0777– 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
| Operacja | Funkcja | Uwagi |
|---|---|---|
| Blokowanie pliku | flock() | Zapobiega konfliktom przy zapisie |
| Kopiowanie | copy() | Nadpisuje, jeśli plik istnieje |
| Przenoszenie / zmiana nazwy | rename() | Działa dla plików i katalogów |
| Usuwanie pliku | unlink() | Bez kosza |
| Tworzenie katalogu | mkdir() | Obsługuje tryby i zagnieżdżenie |
| Usuwanie katalogu | rmdir() | Katalog musi być pusty |
| Analiza ścieżek | basename(), dirname(), pathinfo() | Bardzo pomocne przy pracy z uploadem |
| Przeglądanie katalogów | opendir(), 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
| Operacja | Funkcja | Opis |
|---|---|---|
| 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.
TaskQuest 10 — aplikacja przestaje być jednym plikiem
KARTA PRACY
Plik TaskQuest z Lekcji 9 ma już kilkaset linii: stałą, kilkanaście funkcji, dane, wywołania, style i HTML w jednym miejscu. Żeby zmienić próg XP, musisz przewinąć pół pliku. A to dopiero początek — w kolejnych lekcjach dojdą formularze, baza danych i logowanie.
Cel lekcji
Nauczyć TaskQuest:
- dzielić kod na osobne pliki i „sklejać” go z powrotem (
include,include_once,require,require_once), - budować niezawodne ścieżki do plików (
__DIR__,dirname()), - sprawdzać i przeglądać strukturę własnego projektu (
is_file(),is_dir(),scandir(),pathinfo(),mkdir()).
Lekcja ma dwie części:
- Porządki — dzielimy TaskQuest na pliki. Strona główna ma wyglądać i działać dokładnie tak samo jak po Lekcji 9. To świadoma refaktoryzacja: użytkownik nie widzi różnicy, ale programista widzi ogromną.
- Diagnostyka — dokładamy stronę
diagnostyka.php, która sprawdza, czy pliki projektu leżą tam, gdzie powinny. To pierwsza nowa funkcja TaskQuest 1.0.
Uwaga: dane nadal są zapisane „na sztywno” (symulacja). Plik
data/zadania.phpjest na tym etapie tymczasowy — w Lekcji 11 zastąpimy go prawdziwym plikiem z danymi.
Rozgrzewka — 4 pytania z Lekcji 9
Odpowiedz z pamięci, zanim zaczniesz:
- Dlaczego
$aktywneZadania[0]może nie istnieć, a$aktywneWgTerminu[0]istnieje? - Co musi zwrócić funkcja podana do
array_filter(), żeby element został w wyniku? - Jak zamienić sortowanie w
sortujPoTerminie()z rosnącego na malejące? - Czym różni się
$tytulod$zadanie['tytul']?
1. Wracamy do naszego TaskQuest
Mamy aplikację, która liczy poziom i XP, waliduje login, czyści tytuły, pokazuje terminy, liczy streak i wyświetla posortowaną listę zadań. Wszystkie zmienne i funkcje zostają. Zmienia się tylko to, w którym pliku leżą.
Docelowa struktura projektu
taskquest/
├── index.php
├── diagnostyka.php (nowe)
├── config/
│ └── config.php
├── includes/
│ ├── header.php
│ ├── footer.php
│ ├── funkcje_grywalizacji.php
│ ├── funkcje_tekstowe.php
│ ├── funkcje_terminow.php
│ ├── funkcje_zadan.php
│ └── funkcje_diagnostyczne.php (nowe)
└── data/
└── zadania.php
Co gdzie trafi
| Plik | Zawartość |
|---|---|
config/config.php | define('XP_NA_POZIOM', 100); |
includes/funkcje_grywalizacji.php | obliczPoziom(), pobierzTytul(), obliczXpWAktualnymPoziomie(), obliczProcentPostepu(), ileBrakujeXp() |
includes/funkcje_tekstowe.php | walidujLogin(), wyczyscTytul(), skrocOpis() |
includes/funkcje_terminow.php | komunikatTerminu(), aktualizujStreak() |
includes/funkcje_zadan.php | pobierzWykonaneZadania(), pobierzAktywneZadania(), sumujXp(), sortujPoTerminie() |
data/zadania.php | tablica $zadania |
includes/header.php | początek strony HTML (od <!DOCTYPE> do <h1>TaskQuest</h1>) |
includes/footer.php | koniec strony HTML |
index.php | dane użytkownika i grywalizacji, wywołania funkcji, zawartość panelu |
Przygotowanie (zrób to teraz)
- Utwórz katalog
taskquest/tam, gdzie trzymasz projekty (np. w katalogu serwera lokalnego). - Skopiuj do niego swój plik TaskQuest z Lekcji 9 i nazwij go
index.php. - Zrób kopię zapasową pliku z Lekcji 9 — np.
taskquest_lekcja9.php— poza katalogiemtaskquest/. Będzie potrzebna do testu regresji (sekcja 8). - Uruchom
index.phptak jak w poprzednich lekcjach i zapamiętaj (lub zrób zrzut), jak wygląda strona.
2. Problem w aplikacji
Kama chce zmienić liczbę XP potrzebną na poziom. W jednym długim pliku szuka define('XP_NA_POZIOM', 100) między funkcjami, danymi i kodem HTML. Za kilka lekcji taki plik będzie miał tysiąc linii.
Jest też drugi problem: strona diagnostyczna, którą dołożymy na końcu, potrzebuje tego samego nagłówka HTML co strona główna. Kopiowanie nagłówka do każdej strony oznacza, że każdą zmianę trzeba wprowadzać w kilku miejscach — i któregoś dnia zapomnimy o jednym.
Potrzebujemy:
- sposobu, żeby podzielić kod na małe pliki o jasnym przeznaczeniu,
- sposobu, żeby PHP skleiło je w całość w odpowiedniej kolejności.
3. Nowy mechanizm PHP: dołączanie plików
3.1 Minimalny przykład
W katalogu taskquest/ utwórz dwa pliki brudnopisu.
pozdrowienie.php
<?php
$powitanie = "Witaj w TaskQuest!";
function krzyknij(string $tekst): string
{
return strtoupper($tekst) . "!";
}
brudnopis.php
<?php
require_once __DIR__ . '/pozdrowienie.php';
echo $powitanie . "<br>";
echo krzyknij("start") . "<br>";
Otwórz brudnopis.php. Zobaczysz:
Witaj w TaskQuest!
START!
Co się stało? require_once wstawiło zawartość pozdrowienie.php w miejsce, w którym stoi. Dlatego zmienna $powitanie i funkcja krzyknij() są dostępne, jakby były napisane w brudnopis.php. Tak samo będzie ze stałymi i funkcjami TaskQuest.
3.2 Cztery instrukcje
| Instrukcja | Gdy pliku brakuje | Dołączenie tego samego pliku drugi raz |
|---|---|---|
include | ostrzeżenie (Warning), skrypt działa dalej | dozwolone |
include_once | ostrzeżenie, skrypt działa dalej | plik dołączony tylko raz |
require | błąd krytyczny, skrypt zatrzymuje się | dozwolone |
require_once | błąd krytyczny, skrypt zatrzymuje się | plik dołączony tylko raz |
Zasada kciuka:
- plik, bez którego aplikacja nie ma sensu (konfiguracja, funkcje, nagłówek strony) →
require_once, - plik funkcji zawsze z
_once— druga próba zadeklarowania tej samej funkcji kończy się błędem krytycznym, includetylko dla rzeczy naprawdę opcjonalnych, których brak nie psuje strony.
To instrukcje, a nie zwykłe funkcje — nawiasy są zbędne, dlatego w TaskQuest piszemy require_once __DIR__ . '/plik.php';.
3.3 Ścieżki: względna czy __DIR__?
Ścieżka względna ('includes/header.php', '../config/config.php') jest liczona od katalogu, z którego uruchomiono skrypt — a nie od pliku, w którym ją wpisałaś/wpisałeś. (PHP próbuje przy tym jeszcze kilku dodatkowych miejsc, więc zachowanie bywa zaskakujące — lepiej się na nim nie opierać.) To częste źródło błędów: ten sam kod działa w jednym pliku, a w innym nie.
__DIR__ to stała PHP, która zawsze zawiera pełną ścieżkę do katalogu pliku, w którym jest zapisana. Sklejona z resztą ścieżki daje adres, który działa zawsze:
echo __DIR__; // np. /home/kama/taskquest (dla brudnopis.php)
Dopisz tę linię do brudnopis.php i zobacz, co wypisze. Zapamiętaj wynik — przyda się w eksperymencie.
Dobra praktyka: w plikach zawierających wyłącznie PHP (funkcje, konfiguracja, dane) nie zamykamy tagu
?>. Każdy znak (nawet enter) po?>trafiłby do przeglądarki jako tekst, a przy cookies i sesjach (Lekcja 14) powodowałoby to trudne do znalezienia błędy. W plikachheader.phpifooter.php, które są w dużej części HTML-em, wychodzimy z PHP normalnie.
4. SAMODZIELNIE — bonus XP w brudnopisie
Utwórz plik bonus.php, w którym:
- zdefiniujesz stałą
BONUS_XPo wartości10, - napiszesz funkcję
dodajBonus(int $xp): int, która zwraca$xppowiększone oBONUS_XP.
W brudnopis.php dołącz bonus.php i wypisz wynik dodajBonus(90). Ma wyjść 100.
HINT
Plik z funkcją i stałą wygląda jak każdy inny plik PHP — zaczyna się od <?php. Dołączasz go tak samo jak pozdrowienie.php.
POMOC
<?php
define('...', ...);
function dodajBonus(int $xp): int
{
// zwróć $xp powiększone o stałą
}
ROZWIĄZANIE
bonus.php
<?php
define('BONUS_XP', 10);
function dodajBonus(int $xp): int
{
return $xp + BONUS_XP;
}
brudnopis.php (dopisz pod istniejącym kodem)
require_once __DIR__ . '/bonus.php';
echo dodajBonus(90);
5. SAMODZIELNIE — include czy require?
Bez pisania kodu. Dla każdego pliku TaskQuest wybierz instrukcję (include, include_once, require, require_once) i uzasadnij wybór jednym zdaniem:
| Plik | Twój wybór | Uzasadnienie |
|---|---|---|
config/config.php | ||
includes/funkcje_zadan.php | ||
includes/header.php | ||
includes/ogloszenie.php — hipotetyczny plik z „ogłoszeniem dnia”, który czasem istnieje, a czasem nie |
ROZWIĄZANIE
| Plik | Wybór | Uzasadnienie |
|---|---|---|
config/config.php | require_once | bez stałych TaskQuest nie policzy poziomów; wystarczy jedno dołączenie |
includes/funkcje_zadan.php | require_once | bez funkcji aplikacja nie działa, a ponowne dołączenie = błąd „Cannot redeclare” |
includes/header.php | require_once (require też jest poprawne) | bez nagłówka strona nie ma struktury HTML |
includes/ogloszenie.php | include | brak pliku nie może zepsuć całej strony |
6. Dzielimy TaskQuest — krok po kroku
Zasada pracy: po każdym kroku odśwież stronę. Jeżeli coś przestanie działać, wiesz, że błąd jest w ostatnim kroku. Nie przenoś wszystkiego naraz.
Krok 1 — katalogi
W katalogu taskquest/ utwórz podkatalogi config, includes i data.
Krok 2 — konfiguracja
SAMODZIELNIE
Utwórz config/config.php i przenieś do niego stałą XP_NA_POZIOM z index.php. W index.php w tym miejscu dołącz nowy plik.
HINT
Linię z define(...) wytnij z index.php (nie kopiuj — inaczej stała będzie zdefiniowana dwa razy). Do index.php wpisz w jej miejsce require_once.
ROZWIĄZANIE
config/config.php
<?php
// Ustawienia aplikacji TaskQuest
define('XP_NA_POZIOM', 100);
index.php (na samej górze, tam gdzie była stała)
require_once __DIR__ . '/config/config.php';
Odśwież stronę — wygląda tak samo.
Krok 3 — funkcje grywalizacji
SAMODZIELNIE
Przenieś do includes/funkcje_grywalizacji.php pięć funkcji: obliczPoziom(), pobierzTytul(), obliczXpWAktualnymPoziomie(), obliczProcentPostepu(), ileBrakujeXp(). Same funkcje — wywołania ($poziom = obliczPoziom($xp); itd.) zostają w index.php.
HINT
Nowy plik zaczyna się od <?php. Funkcje przenosisz bez zmian — wycinasz z index.php, wklejasz do nowego pliku. W index.php dołącz plik pod config.php.
POMOC
<?php
// Funkcje systemu XP, poziomów i tytułów
function obliczPoziom(int $xp) : int {
// ... bez zmian
}
// ... pozostałe cztery funkcje
ROZWIĄZANIE
Kompletny plik znajdziesz w sekcji 10. W index.php dopisz pod config.php:
require_once __DIR__ . '/includes/funkcje_grywalizacji.php';
Uwaga na kolejność: wywołania typu
$poziom = obliczPoziom($xp);muszą stać pod wszystkimirequire_once. Inaczej PHP nie będzie znało funkcji ani stałej w chwili wywołania.
Krok 4 — pozostałe funkcje
SAMODZIELNIE
Tak samo jak w kroku 3 przenieś resztę funkcji, po jednym pliku naraz (po każdym odśwież stronę):
| Nowy plik | Funkcje |
|---|---|
includes/funkcje_tekstowe.php | walidujLogin(), wyczyscTytul(), skrocOpis() |
includes/funkcje_terminow.php | komunikatTerminu(), aktualizujStreak() |
includes/funkcje_zadan.php | pobierzWykonaneZadania(), pobierzAktywneZadania(), sumujXp(), sortujPoTerminie() |
W index.php dołącz każdy z nich przez require_once __DIR__ . '/includes/...';.
Po tym kroku w index.php nie ma już żadnej definicji funkcji (słowa function). Sprawdź to wyszukiwaniem w edytorze.
Krok 5 — dane zadań
SAMODZIELNIE
Przenieś tablicę $zadania do data/zadania.php i dołącz ten plik w index.php.
HINT
Przenosisz cały zapis $zadania = [ ... ];. Tablica ma się nadal nazywać $zadania — kod niżej (pobierzWykonaneZadania($zadania) itd.) nie może się zmienić.
ROZWIĄZANIE
Plik data/zadania.php — sekcja 10. W index.php:
require_once __DIR__ . '/data/zadania.php';
Dlaczego $zadania jest widoczna w index.php? Bo require_once wstawia kod pliku dokładnie w to miejsce — zmienna powstaje tak, jakby była napisana w index.php.
Krok 6 — nagłówek i stopka HTML
SAMODZIELNIE
Utwórz includes/header.php i includes/footer.php.
header.php: wszystko od<!DOCTYPE html>do<h1>TaskQuest</h1>(razem z<style>i otwarciem<div class="panel">),footer.php: zamknięcie<div class="panel">,</body>i</html>.
Tytuł strony w <title> ma pochodzić ze zmiennej $nazwaStrony, którą ustawi strona dołączająca nagłówek.
Wyświetlanie panelu (od zdania „Zdobywaj punkty…” do końca listy zadań) zostaje w index.php.
HINT
header.phpto zwykły HTML z jedną wstawką PHP w<title>. Do wypisania zmiennej użyj skróconego<?= ... ; ?>, które już znasz.- Zmienną
$nazwaStronytrzeba ustawić przed dołączeniemheader.php. Pomyśl: skąd nagłówek miałby ją znać, gdyby jeszcze nie istniała?
Uwaga na nazwy: to trzecia rzecz związana z „tytułem” w TaskQuest — obok
$tytul(tytuł gracza) i$zadanie['tytul'](tytuł zadania). Dlatego nazwa strony to$nazwaStrony, a nie$tytul....
POMOC
includes/header.php (początek)
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $nazwaStrony; ?></title>
<style>
/* style bez zmian */
</style>
</head>
<body>
<div class="panel">
<h1>TaskQuest</h1>
index.php — na końcu części PHP, a potem HTML panelu:
$nazwaStrony = 'TaskQuest';
require_once __DIR__ . '/includes/header.php';
?>
<p>Zdobywaj punkty za wykonane zadania!</p>
<!-- reszta panelu bez zmian -->
<?php
require_once __DIR__ . '/includes/footer.php';
ROZWIĄZANIE
Kompletne header.php i footer.php — sekcja 10. Do stopki dodaliśmy linię TaskQuest 1.0 (jedyna różnica na stronie względem Lekcji 9).
Krok 7 — porządek w index.php
Sprawdź, czy index.php ma kolejność:
1. require_once (config, funkcje, dane)
2. dane użytkownika ($imieUzytkownika, $login)
3. dane grywalizacji ($xp, $streak, $dataOstatniejAktywnosci)
4. wywołania funkcji ($poziom, $tytul, ..., $zadanieWykonane, $streak)
5. $nazwaStrony + header
6. zawartość panelu
7. footer
W index.php zostają dane użytkownika i grywalizacji — to na razie dane symulowane, które w kolejnych lekcjach zastąpią prawdziwe źródła. Wywołania funkcji zostają dokładnie takie, jakie były.
7. Kolejne zadanie — strona diagnostyczna
Problem
Plików jest już prawie dziesięć. Jedna literówka w nazwie (funkcje_zadan.php vs funkcje_zadania.php) i aplikacja wyświetla błąd krytyczny, a Ty musisz zgadywać, którego pliku brakuje. Potrzebujemy narzędzia dla programisty, które sprawdzi strukturę projektu.
Nowy mechanizm: funkcje plików i katalogów
Przećwicz w brudnopis.php (dopisz na końcu). Przed uruchomieniem przewidź wynik każdej linii:
$sciezka = __DIR__ . '/data/zadania.php';
echo basename($sciezka) . "<br>"; // nazwa pliku
echo dirname($sciezka) . "<br>"; // katalog, w którym leży
echo pathinfo($sciezka, PATHINFO_EXTENSION) . "<br>"; // rozszerzenie
var_dump(is_file(__DIR__ . '/data/zadania.php')); // czy to plik?
var_dump(is_dir(__DIR__ . '/data')); // czy to katalog?
var_dump(is_file(__DIR__ . '/data')); // ??? (katalog to nie plik)
echo '<pre>';
print_r(scandir(__DIR__ . '/data')); // zawartość katalogu
echo '</pre>';
Ważne cechy scandir():
- zwraca tablicę nazw (posortowaną alfabetycznie),
- zawsze zawiera dwa „udawane” wpisy:
.(ten katalog) i..(katalog nadrzędny) — trzeba je pominąć, - dla katalogu, który nie istnieje, zwraca
falsei wypisuje ostrzeżenie.
Dodatkowo mkdir() tworzy katalog, a rmdir() usuwa pusty katalog. Sprawdź w brudnopisie:
$katalog = __DIR__ . '/tymczasowy';
if (!is_dir($katalog)) {
mkdir($katalog);
}
var_dump(is_dir($katalog)); // true
rmdir($katalog); // sprzątamy po sobie
var_dump(is_dir($katalog)); // false
Zwróć uwagę na wzorzec: najpierw sprawdź (is_dir()), potem działaj (mkdir()).
SAMODZIELNIE — czyPlikIstnieje()
Utwórz plik includes/funkcje_diagnostyczne.php i napisz w nim funkcję:
function czyPlikIstnieje(string $sciezkaWzgledna): bool
Ma zwracać true, jeśli w katalogu projektu (taskquest/) istnieje plik o podanej ścieżce, np. 'config/config.php'.
HINT
Plik leży w includes/, a katalog projektu jest o jeden poziom wyżej. Jedna funkcja zwraca katalog nadrzędny wskazanej ścieżki — użyj jej na __DIR__.
POMOC
function czyPlikIstnieje(string $sciezkaWzgledna): bool
{
// dirname(__DIR__) to katalog projektu
// sklej go z '/' i $sciezkaWzgledna, sprawdź is_file()
}
ROZWIĄZANIE
function czyPlikIstnieje(string $sciezkaWzgledna): bool
{
return is_file(dirname(__DIR__) . '/' . $sciezkaWzgledna);
}
SAMODZIELNIE — listaPlikowWKatalogu()
W tym samym pliku napisz funkcję:
function listaPlikowWKatalogu(string $katalog, string $rozszerzenie = 'php'): array
Zwraca tablicę nazw plików z podanego katalogu projektu, ale:
- pomija
.i.., - bierze tylko pliki (nie katalogi) o podanym rozszerzeniu (domyślnie
php), - dla katalogu, który nie istnieje, zwraca pustą tablicę bez żadnego ostrzeżenia.
(Rozszerzenie jako parametr przyda się w Lekcji 11, gdy w data/ pojawią się pliki innego typu.)
HINT
- Zacznij od zabezpieczenia:
is_dir()przedscandir(). .i..pomijasz przezcontinue— znasz je z pętli.- Rozszerzenie odczytasz przez
pathinfo(..., PATHINFO_EXTENSION).
POMOC
function listaPlikowWKatalogu(string $katalog, string $rozszerzenie = 'php'): array
{
$sciezka = dirname(__DIR__) . '/' . $katalog;
$pliki = [];
// 1. jeśli to nie katalog → zwróć $pliki (pustą tablicę)
foreach (scandir($sciezka) as $element) {
// 2. pomiń . i ..
// 3. jeśli to plik z właściwym rozszerzeniem → dopisz do $pliki
}
return $pliki;
}
ROZWIĄZANIE
function listaPlikowWKatalogu(string $katalog, string $rozszerzenie = 'php'): array
{
$sciezka = dirname(__DIR__) . '/' . $katalog;
$pliki = [];
if (!is_dir($sciezka)) {
return $pliki;
}
foreach (scandir($sciezka) as $element) {
if ($element === '.' || $element === '..') {
continue;
}
if (is_file($sciezka . '/' . $element) && pathinfo($element, PATHINFO_EXTENSION) === $rozszerzenie) {
$pliki[] = $element;
}
}
return $pliki;
}
SAMODZIELNIE — strona diagnostyka.php
W katalogu głównym (taskquest/) utwórz diagnostyka.php. Strona ma:
- korzystać z tych samych
header.phpifooter.phpco strona główna (to jest właśnie zysk z Kroku 6!), - dla każdego pliku z tablicy
$wymaganePliki(10 plików z docelowej struktury) wypisać[OK]lub[BRAK]i jego ścieżkę, - dla katalogów
config,includesidatawypisać liczbę plików.phpi ich nazwy.
Ważne: diagnostyka.php dołącza tylko funkcje_diagnostyczne.php (oraz header i footer). Nie dołącza config.php ani funkcji aplikacji. Dlaczego? Bo ma działać nawet wtedy, gdy reszta projektu jest zepsuta — inaczej nie mogłaby jej diagnozować.
HINT
Dwie pętle foreach: jedna po $wymaganePliki, druga po $katalogiDoPrzegladu = ['config', 'includes', 'data']. Znacznik [OK]/[BRAK] zrobisz operatorem ? : — jak [X]/[ ] w liście zadań.
POMOC
<?php
require_once __DIR__ . '/includes/funkcje_diagnostyczne.php';
$wymaganePliki = [
'index.php',
'config/config.php',
// ... pozostałe
];
$katalogiDoPrzegladu = ['config', 'includes', 'data'];
$nazwaStrony = 'TaskQuest — diagnostyka';
require_once __DIR__ . '/includes/header.php';
?>
<h2>Diagnostyka projektu</h2>
<!-- pętle foreach -->
<?php
require_once __DIR__ . '/includes/footer.php';
ROZWIĄZANIE
Sekcja 10.
Uwaga: to narzędzie dla programisty, a nie dla użytkownika aplikacji. Pokazywanie budowy projektu wszystkim to zły pomysł — do zagadnienia tego, co wolno pokazywać użytkownikowi, wrócimy w lekcji o bezpieczeństwie.
8. Testy
Przed uruchomieniem spróbuj przewidzieć wynik.
Test 1 — struktura (strona diagnostyka.php)
| Co sprawdzamy | Oczekiwany wynik |
|---|---|
| lista wymaganych plików | wszystkie 10 z [OK] |
config/ | 1 plik: config.php |
includes/ | 7 plików |
data/ | 1 plik: zadania.php |
Test 2 — regresja: strona główna działa jak po Lekcji 9
Otwórz obok siebie stronę z kopii taskquest_lekcja9.php i nową index.php. Jedyna różnica: linia TaskQuest 1.0 na dole.
Wartości niezależne od daty (przy $xp = 99):
| Co sprawdzamy | Oczekiwany wynik |
|---|---|
| Poziom / Tytuł | 1 / Nowicjusz |
| Nagłówek postępu | Masz 99 XP z 100 XP potrzebnych do poziomu 2 (99%) |
| Brakuje XP | 1 |
| Wszystkie / Wykonane / Aktywne | 5 / 2 / 3 |
| XP do zdobycia z aktywnych | 55 |
| Komunikat awansu | awansujesz na poziom 2 |
Kolejność id na liście | 2, 3, 1, 5, 4 |
Komunikaty terminów (minął ... dni temu, pozostało ... dni) i wartość streaka zależą od dnia uruchomienia — porównaj je z kopią z Lekcji 9 uruchomioną tego samego dnia.
Test 3 — jedna zmiana w config.php
Zmień w config/config.php wartość XP_NA_POZIOM (nic więcej nie ruszaj!):
XP_NA_POZIOM | Poziom | Tytuł | Nagłówek postępu | Kolejny próg | Brakuje XP | Komunikat |
|---|---|---|---|---|---|---|
| 100 | 1 | Nowicjusz | 99 XP z 100 … do poziomu 2 (99%) | 100 | 1 | awansujesz na poziom 2 |
| 200 | 1 | Nowicjusz | 99 XP z 200 … do poziomu 2 (49.5%) | 200 | 101 | nie wystarczą — brakuje 46 XP |
| 50 | 2 | Wojownik | 49 XP z 50 … do poziomu 3 (98%) | 100 | 1 | awansujesz na poziom 3 |
Przywróć 100.
Test 4 — funkcje diagnostyczne
| Wywołanie | Oczekiwany wynik |
|---|---|
czyPlikIstnieje('index.php') | true |
czyPlikIstnieje('config/config.php') | true |
czyPlikIstnieje('config') | false (to katalog, nie plik) |
czyPlikIstnieje('nie_ma.php') | false |
listaPlikowWKatalogu('data') | ['zadania.php'] |
listaPlikowWKatalogu('data', 'csv') | [] |
listaPlikowWKatalogu('nie_ma') | [], bez ostrzeżenia |
listaPlikowWKatalogu('includes') | footer.php, funkcje_diagnostyczne.php, funkcje_grywalizacji.php, funkcje_tekstowe.php, funkcje_terminow.php, funkcje_zadan.php, header.php (alfabetycznie) |
Test 5 — liczby z Lekcji 9 nadal się zgadzają
| Co sprawdzamy | Wynik |
|---|---|
count($zadania) | 5 |
$liczbaWykonanych / $liczbaAktywnych | 2 / 3 |
sumujXp($zadania) / sumujXp($aktywneZadania) / sumujXp($wykonaneZadania) | 100 / 55 / 45 |
sumujXp([]) | 0 |
Test 6 — diagnostyka wykrywa problem
Zmień nazwę includes/funkcje_zadan.php na includes/funkcje_zadania.php.
index.phpprzestaje działać (błąd krytyczny),diagnostyka.phpnadal działa i pokazuje[BRAK] includes/funkcje_zadan.php.
Przywróć poprawną nazwę.
9. Obowiązkowe eksperymenty
Po każdym eksperymencie przywróć projekt do stanu działającego.
Eksperyment 1: dwa razy ten sam plik
W index.php zamień require_once dla funkcje_zadan.php na zwykły require i wpisz tę linię dwa razy. Co zobaczysz? Dlaczego? Co robił _once?
Oczekiwany efekt: błąd krytyczny „Cannot redeclare pobierzWykonaneZadania()”.
Eksperyment 2: brakujący plik — include czy require_once?
Zmień nazwę includes/funkcje_zadan.php na includes/x.php.
a) W index.php zastąp dla tego pliku require_once przez include. Odśwież. Ile komunikatów zobaczysz? W którym miejscu kończy się działanie skryptu i jaki błąd jest widoczny?
b) Wróć do require_once. Odśwież. Porównaj komunikat.
Odpowiedz: który wariant szybciej i wyraźniej wskazuje prawdziwą przyczynę problemu?
Oczekiwany efekt: przy include najpierw ostrzeżenie o braku pliku, a dopiero później (w linii, gdzie wywołujesz funkcję) błąd „Call to undefined function”. Przy require_once skrypt zatrzymuje się od razu.
Eksperyment 3: ../ a __DIR__
Na samą górę includes/header.php dopisz:
<?php require_once '../config/config.php'; ?>
Odśwież stronę główną. Dlaczego PHP nie znajduje pliku, skoro config/ jest „obok” katalogu includes/? Wskazówka: przypomnij sobie sekcję 3.3 — od czego liczona jest ścieżka względna?
Teraz zamień linię na:
<?php require_once __DIR__ . '/../config/config.php'; ?>
Działa? Czy stała XP_NA_POZIOM została zdefiniowana drugi raz? (Nie — require_once rozpoznaje, że to ten sam plik.)
Eksperyment 4 (dodatkowy): zmienna zapomniana przed nagłówkiem
Usuń z index.php linię $nazwaStrony = 'TaskQuest';. Jaki komunikat zobaczysz i w którym pliku? Co to mówi o tym, jak dołączone pliki „widzą” zmienne?
Eksperyment 5 (dodatkowy): scandir() bez is_dir()
W listaPlikowWKatalogu() zakomentuj sprawdzenie is_dir() i wywołaj funkcję dla 'nie_ma'. Ile ostrzeżeń zobaczysz i skąd drugie? (Wskazówka: co zwróciło scandir() i co próbowała zrobić pętla foreach?)
10. ROZWIĄZANIE — wersja referencyjna
Pełny kod TaskQuest po Lekcji 10. Wszystkie funkcje i zmienne pochodzą z Lekcji 9 — zmienił się tylko podział na pliki. Nowe elementy: header.php, footer.php, $nazwaStrony, funkcje_diagnostyczne.php, diagnostyka.php.
Rozwiązanie
index.php
<?php
// --- pliki aplikacji ---
require_once __DIR__ . '/config/config.php';
require_once __DIR__ . '/includes/funkcje_grywalizacji.php';
require_once __DIR__ . '/includes/funkcje_tekstowe.php';
require_once __DIR__ . '/includes/funkcje_terminow.php';
require_once __DIR__ . '/includes/funkcje_zadan.php';
require_once __DIR__ . '/data/zadania.php';
// --- dane użytkownika ---
$imieUzytkownika = "Kama";
$login = "Kama_2026";
// --- dane grywalizacji (symulowane) ---
$xp = 99;
$streak = 5;
$dataOstatniejAktywnosci = "2026-09-14";
// --- wywołania funkcji: grywalizacja ---
$poziom = obliczPoziom($xp);
$tytul = pobierzTytul($poziom);
$xpWAktualnymPoziomie = obliczXpWAktualnymPoziomie($xp, $poziom);
$procentPostepu = obliczProcentPostepu($xpWAktualnymPoziomie);
$brakujeXp = ileBrakujeXp($xp, $poziom);
//pasek postępu
$liczbaAktywnychSegmentow = (int) ($procentPostepu / 10);
$progNastepnegoPoziomu = $poziom * XP_NA_POZIOM;
// --- wywołania funkcji: lista zadań ---
$wykonaneZadania = pobierzWykonaneZadania($zadania);
$aktywneZadania = pobierzAktywneZadania($zadania);
$liczbaWszystkich = count($zadania);
$liczbaWykonanych = count($wykonaneZadania);
$liczbaAktywnych = count($aktywneZadania);
$xpDoZdobycia = sumujXp($aktywneZadania);
$zadaniaWgTerminu = sortujPoTerminie($zadania);
$aktywneWgTerminu = sortujPoTerminie($aktywneZadania);
// uproszczenie (symulacja): aktywność = wykonane jakiekolwiek zadanie z listy
$zadanieWykonane = $liczbaWykonanych > 0;
$streak = aktualizujStreak($streak, $dataOstatniejAktywnosci, $zadanieWykonane);
// --- prezentacja ---
$nazwaStrony = 'TaskQuest';
require_once __DIR__ . '/includes/header.php';
?>
<p>Zdobywaj punkty za wykonane zadania!</p>
<?php
if (walidujLogin($login)) {
echo "<p>Login poprawny: $login</p>";
} else {
echo "<p>Login jest niepoprawny.</p>";
}
?>
<h2>Witaj, <?= $imieUzytkownika; ?>!</h2>
<p>Poziom: <strong><?= $poziom; ?></strong></p>
<p>Tytuł: <strong><?= $tytul; ?></strong></p>
<p>Łączne XP: <strong><?= $xp; ?></strong></p>
<h3>
<?= "Masz $xpWAktualnymPoziomie XP z " . XP_NA_POZIOM . " XP potrzebnych do poziomu " . ($poziom + 1) . " (" . round($procentPostepu, 2) . "%)"; ?>
</h3>
<div class="pasek-xp">
<?php
for ($segment = 1; $segment <= 10; $segment++) {
if ($segment <= $liczbaAktywnychSegmentow) {
echo "█";
} else {
echo "░";
}
}
?>
</div>
<p>Kolejny próg: <?= $progNastepnegoPoziomu; ?> XP</p>
<p>Brakuje XP: <strong><?= $brakujeXp; ?></strong></p>
<p>Streak: <strong><?= $streak ?></strong></p>
<h2>Twoje zadania</h2>
<p>
Wszystkie: <strong><?= $liczbaWszystkich; ?></strong> |
Wykonane: <strong><?= $liczbaWykonanych; ?></strong> |
Aktywne: <strong><?= $liczbaAktywnych; ?></strong>
</p>
<p>XP do zdobycia z aktywnych zadań: <strong><?= $xpDoZdobycia; ?></strong></p>
<?php
if ($liczbaWszystkich === 0) {
echo "<p>Brak zadań.</p>";
} elseif ($liczbaAktywnych === 0) {
echo "<p>Wszystkie zadania wykonane — świetna robota!</p>";
} else {
if ($xpDoZdobycia >= $brakujeXp) {
echo "<p class=\"awans\">Wykonując aktywne zadania, awansujesz na poziom " . ($poziom + 1) . "!</p>";
} else {
echo "<p>Aktywne zadania nie wystarczą jeszcze na awans — brakuje " . ($brakujeXp - $xpDoZdobycia) . " XP.</p>";
}
$najpilniejsze = $aktywneWgTerminu[0];
echo "<p>Najpilniejsze zadanie: <strong>" . wyczyscTytul($najpilniejsze['tytul']) . "</strong> — "
. komunikatTerminu($najpilniejsze['termin']) . "</p>";
}
echo "<h3>Lista zadań (wg terminu)</h3>";
foreach ($zadaniaWgTerminu as $zadanie) {
$tytulCzysty = wyczyscTytul($zadanie['tytul']);
$znacznik = $zadanie['wykonane'] ? "[X]" : "[ ]";
$klasa = $zadanie['wykonane'] ? "zadanie wykonane" : "zadanie";
echo "<div class=\"$klasa\">";
echo "<p><strong>$znacznik $tytulCzysty</strong> — {$zadanie['xp']} XP</p>";
echo "<p>" . skrocOpis($zadanie['opis'], 50) . "</p>";
echo "<p>Termin: {$zadanie['termin']}";
if (!$zadanie['wykonane']) {
echo " — " . komunikatTerminu($zadanie['termin']);
}
echo "</p>";
if (stripos($tytulCzysty, 'PHP') !== false) {
echo "<p>To zadanie dotyczy PHP.</p>";
}
echo "</div>";
}
?>
<?php
require_once __DIR__ . '/includes/footer.php';
config/config.php
<?php
// Ustawienia aplikacji TaskQuest
define('XP_NA_POZIOM', 100);
includes/funkcje_grywalizacji.php
<?php
// Funkcje systemu XP, poziomów i tytułów
function obliczPoziom(int $xp) : int {
$poziom = 1;
while ($xp >= $poziom * XP_NA_POZIOM) {
$poziom++;
}
return $poziom;
}
function pobierzTytul(int $poziom) : string {
return match ($poziom) {
1 => "Nowicjusz",
2 => "Wojownik",
default => "Mistrz"
};
}
function obliczXpWAktualnymPoziomie(int $xp, int $poziom) : int {
$xpNaPoczatkuPoziomu = ($poziom - 1) * XP_NA_POZIOM;
$xpWAktualnymPoziomie = $xp - $xpNaPoczatkuPoziomu;
return $xpWAktualnymPoziomie;
}
function obliczProcentPostepu(int $xpWAktualnymPoziomie) : float {
return ($xpWAktualnymPoziomie / XP_NA_POZIOM) * 100;
}
function ileBrakujeXp(int $xp, int $poziom): int
{
$progNastepnegoPoziomu = $poziom * XP_NA_POZIOM;
return $progNastepnegoPoziomu - $xp;
}
includes/funkcje_tekstowe.php
<?php
// Funkcje przygotowujące i sprawdzające tekst (login, tytuł, opis)
function walidujLogin(string $login): bool {
return preg_match('/^[a-zA-Z0-9_-]{3,20}$/', $login) === 1;
}
function wyczyscTytul(string $tytul): string
{
$tytul = trim(preg_replace('/\s+/', ' ', $tytul));
return $tytul;
}
function skrocOpis(string $opis, int $limit = 50): string {
if (mb_strlen($opis) <= $limit) {
return $opis;
}
return mb_substr($opis, 0, $limit) . "...";
}
includes/funkcje_terminow.php
<?php
// Funkcje związane z datami: terminy zadań i streak
function komunikatTerminu(string $terminZadania): string{
$dzisiaj = new DateTime('today');
$termin = new DateTime($terminZadania);
$roznica = $dzisiaj->diff($termin);
if ($termin < $dzisiaj) {
return "Termin minął {$roznica->days} dni temu!";
} elseif ($termin == $dzisiaj) {
return "Termin jest dzisiaj.";
} else{
return "Do terminu pozostało: {$roznica->days} dni.";
}
}
function aktualizujStreak(int $streak, string $dataOstatniejAktywnosci, bool $zadanieWykonane): int {
if (!$zadanieWykonane) return $streak;
$dzisiaj = new DateTime('today');
$wczoraj = new DateTime('yesterday');
$ostatniaAktywnosc = new DateTime($dataOstatniejAktywnosci);
if ($ostatniaAktywnosc->format('Y-m-d') === $dzisiaj->format('Y-m-d')) return $streak;
if ($ostatniaAktywnosc->format('Y-m-d') === $wczoraj->format('Y-m-d')) return $streak + 1;
return 1;
}
includes/funkcje_zadan.php
<?php
// Funkcje pracujące na liście zadań
function pobierzWykonaneZadania(array $zadania): array
{
return array_filter($zadania, function ($zadanie) {
return $zadanie['wykonane'];
});
}
function pobierzAktywneZadania(array $zadania): array
{
return array_filter($zadania, function ($zadanie) {
return !$zadanie['wykonane'];
});
}
function sumujXp(array $zadania): int
{
$suma = 0;
foreach ($zadania as $zadanie) {
$suma += $zadanie['xp'];
}
return $suma;
}
function sortujPoTerminie(array $zadania): array
{
usort($zadania, function ($zadanieA, $zadanieB) {
return $zadanieA['termin'] <=> $zadanieB['termin'];
});
return $zadania;
}
data/zadania.php
<?php
// Lista zadań TaskQuest — na razie zapisana "na sztywno" (dane symulowane).
// W Lekcji 11 zastąpimy ją prawdziwym plikiem z danymi.
$zadania = [
[
'id' => 1,
'tytul' => ' Nauczyć się regexów PHP ',
'opis' => 'Powtórzyć wyrażenia regularne przed następną lekcją.',
'termin' => '2026-09-20',
'wykonane' => true,
'xp' => 20
],
[
'id' => 2,
'tytul' => 'Powtórzyć funkcje',
'opis' => 'Przejrzeć wszystkie funkcje TaskQuest i sprawdzić ich parametry oraz typy zwracanych wartości.',
'termin' => '2026-09-16',
'wykonane' => false,
'xp' => 15
],
[
'id' => 3,
'tytul' => 'Ćwiczenia z DateTime',
'opis' => 'Napisać trzy własne przykłady z diff() i format().',
'termin' => '2026-09-18',
'wykonane' => false,
'xp' => 10
],
[
'id' => 4,
'tytul' => 'Nauczyć się tablic PHP',
'opis' => 'Zbudować listę zadań TaskQuest jako tablicę tablic asocjacyjnych.',
'termin' => '2026-09-25',
'wykonane' => false,
'xp' => 30
],
[
'id' => 5,
'tytul' => 'Przygotować projekt do Lekcji 10',
'opis' => 'Zastanowić się, jak podzielić TaskQuest na pliki i katalogi.',
'termin' => '2026-09-22',
'wykonane' => true,
'xp' => 25
]
];
includes/header.php
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $nazwaStrony; ?></title>
<style>
body { font-family: Arial, sans-serif; max-width: 700px; margin: 40px auto; padding: 20px; }
.panel { border: 1px solid #ccc; padding: 24px; border-radius: 12px; }
.pasek-xp { display: flex; gap: 4px; margin: 10px 0; }
.segment { width: 32px; height: 18px; background: #ddd; border-radius: 3px; }
.segment.aktywny { background: #555; }
.awans { font-weight: bold; }
.zadanie { border: 1px solid #ddd; border-radius: 8px; padding: 4px 16px; margin: 10px 0; }
.zadanie.wykonane { background: #f2f2f2; color: #666; }
</style>
</head>
<body>
<div class="panel">
<h1>TaskQuest</h1>
includes/footer.php
<p>TaskQuest 1.0</p>
</div>
</body>
</html>
includes/funkcje_diagnostyczne.php
<?php
// Funkcje pomocnicze dla strony diagnostycznej (narzędzie dla programisty)
function czyPlikIstnieje(string $sciezkaWzgledna): bool
{
return is_file(dirname(__DIR__) . '/' . $sciezkaWzgledna);
}
function listaPlikowWKatalogu(string $katalog, string $rozszerzenie = 'php'): array
{
$sciezka = dirname(__DIR__) . '/' . $katalog;
$pliki = [];
if (!is_dir($sciezka)) {
return $pliki;
}
foreach (scandir($sciezka) as $element) {
if ($element === '.' || $element === '..') {
continue;
}
if (is_file($sciezka . '/' . $element) && pathinfo($element, PATHINFO_EXTENSION) === $rozszerzenie) {
$pliki[] = $element;
}
}
return $pliki;
}
diagnostyka.php
<?php
require_once __DIR__ . '/includes/funkcje_diagnostyczne.php';
$wymaganePliki = [
'index.php',
'config/config.php',
'includes/header.php',
'includes/footer.php',
'includes/funkcje_grywalizacji.php',
'includes/funkcje_tekstowe.php',
'includes/funkcje_terminow.php',
'includes/funkcje_zadan.php',
'includes/funkcje_diagnostyczne.php',
'data/zadania.php'
];
$katalogiDoPrzegladu = ['config', 'includes', 'data'];
$nazwaStrony = 'TaskQuest — diagnostyka';
require_once __DIR__ . '/includes/header.php';
?>
<h2>Diagnostyka projektu</h2>
<h3>Wymagane pliki</h3>
<?php
foreach ($wymaganePliki as $plik) {
$znacznik = czyPlikIstnieje($plik) ? "[OK]" : "[BRAK]";
echo "<p>$znacznik $plik</p>";
}
?>
<h3>Zawartość katalogów (pliki .php)</h3>
<?php
foreach ($katalogiDoPrzegladu as $katalog) {
$pliki = listaPlikowWKatalogu($katalog);
echo "<p><strong>$katalog/</strong> — " . count($pliki) . " plik(ów)</p>";
foreach ($pliki as $plik) {
echo "<p> $plik</p>";
}
}
?>
<?php
require_once __DIR__ . '/includes/footer.php';
Pliki brudnopisu (brudnopis.php, pozdrowienie.php, bonus.php) możesz usunąć.
11. Co zmieniliśmy w TaskQuest?
Zostaje
$imieUzytkownika, $login
$xp, $poziom, $tytul, $xpWAktualnymPoziomie, $procentPostepu, $brakujeXp,
$liczbaAktywnychSegmentow, $progNastepnegoPoziomu
$streak, $dataOstatniejAktywnosci, $zadanieWykonane
$zadania, $wykonaneZadania, $aktywneZadania, $liczbaWszystkich, $liczbaWykonanych,
$liczbaAktywnych, $zadaniaWgTerminu, $aktywneWgTerminu, $xpDoZdobycia
define('XP_NA_POZIOM', 100)
wszystkie 14 funkcji z poprzednich lekcji (kod bez zmian)
Dochodzi
$nazwaStrony // tytuł strony dla header.php
$wymaganePliki, $katalogiDoPrzegladu // diagnostyka.php
czyPlikIstnieje(), listaPlikowWKatalogu() // includes/funkcje_diagnostyczne.php
nowe pliki: config/config.php, includes/*.php (7), data/zadania.php, diagnostyka.php
Nowe narzędzia PHP: require_once, include, require, include_once, __DIR__, dirname(), is_file(), is_dir(), scandir(), pathinfo(), basename(), mkdir(), rmdir().
Zmieniamy
- miejsce w kodzie: stała, funkcje, dane zadań i szkielet HTML przeniesione z
index.phpdo osobnych plików (logika i wygląd strony głównej bez zmian), index.phpzaczyna się od blokurequire_oncei kończy dołączeniem stopki.
Usuwamy
Nic. Zmiany dotyczą wyłącznie podziału na pliki — żadna zmienna ani funkcja nie została usunięta.
Co nowego potrafi TaskQuest po tej lekcji?
Po Lekcji 10 TaskQuest jest aplikacją wieloplikową: konfiguracja, funkcje, dane i szkielet strony leżą w osobnych plikach, a jedna zmiana (np. progu XP) dotyczy jednego miejsca. Strona
diagnostyka.phpsprawdza, czy wszystkie wymagane pliki istnieją, i wyświetla zawartość katalogów projektu.
Do samodzielnego sprawdzenia
[ ] Nadal rozwijam ten sam projekt TaskQuest (nie zaczynam nowego).
[ ] Mam katalogi config/, includes/ i data/.
[ ] W index.php nie ma żadnej definicji funkcji ani stałej XP_NA_POZIOM.
[ ] Wszystkie pliki z funkcjami i konfiguracją dołączam przez require_once i __DIR__.
[ ] Potrafię wyjaśnić różnicę między include a require oraz po co jest _once.
[ ] Wiem, dlaczego ścieżka z __DIR__ jest pewniejsza niż względna.
[ ] Strona główna wygląda i działa jak po Lekcji 9 (jedyna różnica: linia "TaskQuest 1.0").
[ ] Zmiana XP_NA_POZIOM w jednym pliku zmienia zachowanie całej aplikacji.
[ ] Mam header.php i footer.php, a $nazwaStrony ustawiam przed dołączeniem nagłówka.
[ ] Napisałam/napisałem czyPlikIstnieje() i listaPlikowWKatalogu().
[ ] Strona diagnostyka.php pokazuje 10 razy [OK].
[ ] Przeprowadziłam/przeprowadziłem eksperymenty i przywróciłam/przywróciłem projekt do stanu działającego.
[ ] Usunęłam/usunąłem pliki brudnopisu.
Pytania kontrolne
- Podaj dwa konkretne powody, dla których podzieliliśmy TaskQuest na pliki.
- Co się stanie po
include 'nie_ma.php';, a co porequire 'nie_ma.php';? Który wariant jest groźniejszy w pliku z funkcjami — i dlaczego? - Dlaczego pliki z funkcjami dołączamy przez
require_once, a nierequire? Co zobaczysz, jeśli dołączysz taki plik dwa razy? - Co przechowuje
__DIR__w plikuindex.php, a co wincludes/header.php? - Dlaczego
require_once '../config/config.php';wheader.phpnie działa, arequire_once __DIR__ . '/../config/config.php';działa? - Skąd
header.phpzna zmienną$nazwaStrony? Co się stanie, jeśli ustawisz ją po dołączeniu nagłówka? - Co zwraca
scandir()oprócz nazw plików? Jak sobie z tym radzimy i co funkcja zwróci dla nieistniejącego katalogu? czyPlikIstnieje('config')zwrócitrueczyfalse? Dlaczego?- Dlaczego
diagnostyka.phpnie dołączaconfig.phpani funkcji aplikacji? - Zmieniasz
XP_NA_POZIOMwconfig/config.phpz 100 na 50. Które pliki musisz jeszcze zmienić, żeby cała aplikacja liczyła poziomy po nowemu? Co to mówi o zaletach podziału na pliki?
Zadanie końcowe — rozbuduj TaskQuest samodzielnie
Wybierz jedno (albo oba):
A) Style w osobnym pliku. Utwórz assets/css/style.css, przenieś do niego zawartość <style> z header.php i podłącz plik tagiem <link rel="stylesheet" href="assets/css/style.css">. Dodaj 'assets/css/style.css' do listy $wymaganePliki w diagnostyce. Zauważ: href w HTML nie używa __DIR__ — to adres, który pobiera przeglądarka, a nie ścieżka, którą czyta PHP. Pomyśl, dlaczego.
B) Katalog logs/. Dodaj do diagnostyki sprawdzanie, czy istnieje katalog logs/. Jeśli nie — utwórz go przez mkdir() (najpierw is_dir()!) i wyświetl komunikat „Utworzono katalog logs/”, a przy kolejnym odświeżeniu „Katalog logs/ istnieje”.
Nie potrzebujesz niczego, czego nie poznałaś/poznałeś do tej pory.
Most do Lekcji 11
Kod TaskQuest jest już uporządkowany, ale zadania nadal są częścią programu:
data/zadania.php → $zadania = [ ... ]; (kod PHP)
Żeby dodać albo zmienić zadanie, musisz edytować kod. Każde zadanie napisane w formularzu (Lekcja 13) musiałoby zmieniać kod aplikacji — a to nie jest dobry pomysł.
Następny problem:
Jak oddzielić dane od programu — czyli przechowywać zadania w prawdziwym pliku, który TaskQuest potrafi odczytać i zapisać?
PODSUMOWANIE LEKCJI
W skrócie
include/requirewstawiają kod pliku w miejsce, w którym stoją. Zmienne, funkcje i stałe z dołączonego pliku istnieją dopiero od tej linii w dół.- Kolejność ma znaczenie: najpierw
require_once, potem wywołania funkcji. Zmienną używaną w dołączanym pliku (np.$nazwaStronywheader.php) ustawiamy przed jego dołączeniem. - Brak pliku:
include→ ostrzeżenie i skrypt działa dalej (błąd wychodzi później, w innym miejscu),require→ skrypt zatrzymuje się od razu. _oncepilnuje, żeby plik był dołączony tylko raz. Funkcji nie da się zadeklarować dwa razy → błąd krytycznyCannot redeclare. Działa to tylko między instrukcjami_once— zwykłyrequireporequire_oncedołączy plik ponownie.- Ścieżka względna w
include/requirenie jest liczona od pliku, w którym ją zapisano.__DIR__= katalog pliku, w którym stała jest zapisana — dlatego ścieżka__DIR__ . '/...'działa zawsze. dirname(__DIR__)= katalog o poziom wyżej (w TaskQuest: zincludes/do katalogu projektu).- Po
__DIR__ścieżka musi zaczynać się od/:__DIR__ . '/config/config.php'. __DIR__to ścieżka na dysku serwera — służy PHP. Whrefisrcw HTML podajemy adres dla przeglądarki (np.styl.css), bez__DIR__.scandir()zwraca tablicę nazw razem z.i... Dla nieistniejącego katalogu zwracafalsei ostrzeżenie → najpierwis_dir().is_file()zwracatruetylko dla pliku,is_dir()tylko dla katalogu.mkdir()na istniejącym katalogu → ostrzeżenie. Zagnieżdżone katalogi tylko z trzecim parametremtrue.rmdir()usuwa wyłącznie pusty katalog.- W plikach zawierających tylko PHP nie zamykamy tagu
?>. - Nigdy nie wstawiamy do
includedanych od użytkownika bez sprawdzenia (lista dozwolonych wartości).
Co trzeba umieć na INF.03?
- poprawnie połączyć zewnętrzny plik CSS i obrazy ścieżką względną (
href="styl.css",src="obraz.jpg") i odróżniać ją od ścieżki do pliku, którą czyta PHP, - rozumieć podział strony na bloki (nagłówek, panel boczny, część główna, stopka) — arkusz jest zbudowany właśnie z takich bloków,
- rozpoznać, dlaczego skrypt kończy się błędem „Call to undefined function” albo „Failed opening required” i gdzie szukać przyczyny,
- znać różnicę między
includearequire— to podstawowa wiedza o PHP, ale w materiałach projektu nie ma potwierdzenia, że jest sprawdzana na egzaminie.
Minimum, które powinno wejść w pamięć
- Pliki z funkcjami i konfiguracją:
require_once __DIR__ . '/sciezka/plik.php'; includeprzy braku pliku → ostrzeżenie, działa dalej.require→ zatrzymuje skrypt._once= tylko raz. Dwa razy ta sama funkcja →Cannot redeclare.- Najpierw dołącz, potem używaj. Zmienne dla nagłówka ustaw przed jego dołączeniem.
__DIR__= katalog bieżącego pliku,dirname(__DIR__)= poziom wyżej.scandir()zawsze zawiera.i... Przedscandir()imkdir()sprawdźis_dir().rmdir()usuwa tylko pusty katalog.
Ściągawka – najważniejsze polecenia i składnia
Dołączanie plików
| Instrukcja | Brak pliku | Drugie dołączenie |
|---|---|---|
include | ostrzeżenie, działa dalej | tak |
include_once | ostrzeżenie, działa dalej | nie |
require | błąd krytyczny, stop | tak |
require_once | błąd krytyczny, stop | nie |
require_once __DIR__ . '/config/config.php'; // z pliku w katalogu głównym
require_once __DIR__ . '/../config/config.php'; // z pliku w includes/
Ścieżki
| Zapis | Wynik dla /var/www/taskquest/data/zadania.php |
|---|---|
basename($s) | zadania.php |
basename($s, '.php') | zadania |
dirname($s) | /var/www/taskquest/data |
pathinfo($s, PATHINFO_EXTENSION) | php |
pathinfo($s, PATHINFO_FILENAME) | zadania |
__DIR__ | katalog pliku, w którym stała jest zapisana |
__FILE__ | pełna ścieżka pliku, w którym stała jest zapisana |
Pliki i katalogi
| Funkcja | Zwraca / robi |
|---|---|
is_file($s) | true tylko dla pliku |
is_dir($s) | true tylko dla katalogu |
scandir($s) | tablica nazw (z . i ..) lub false |
mkdir($s) | tworzy katalog |
mkdir($s, 0755, true) | tworzy także brakujące katalogi po drodze |
rmdir($s) | usuwa pusty katalog |
getcwd() / chdir($s) | bieżący katalog roboczy / zmiana katalogu |
Schematy
// utwórz katalog tylko jeśli go nie ma
if (!is_dir($katalog)) {
mkdir($katalog);
}
// przejście po katalogu bez . i ..
foreach (scandir($katalog) as $element) {
if ($element === '.' || $element === '..') {
continue;
}
// ...
}
Szkielet strony z nagłówkiem i stopką
$nazwaStrony = '...';
require_once __DIR__ . '/includes/header.php';
?>
<!-- treść strony -->
<?php
require_once __DIR__ . '/includes/footer.php';
Quiz – sprawdź się
1. Co zostanie wyświetlone? Pliku brak.php nie ma.
include 'brak.php';
echo "A";
require 'brak.php';
echo "B";
2. Plik a.php zawiera tylko <?php $licznik = 1;. Co wyświetli poniższy kod?
$licznik = 5;
require __DIR__ . '/a.php';
echo $licznik;
3. Plik funkcje.php zawiera definicję funkcji. W której linii pojawi się błąd krytyczny i jaki?
require_once __DIR__ . '/funkcje.php'; // linia 1
require_once __DIR__ . '/funkcje.php'; // linia 2
require __DIR__ . '/funkcje.php'; // linia 3
4. Dlaczego ten kod nie działa? Jak go poprawić?
require_once __DIR__ . '/config/config.php';
$poziom = obliczPoziom(150);
require_once __DIR__ . '/includes/funkcje_grywalizacji.php';
5. Plik ma ścieżkę /var/www/taskquest/includes/funkcje_diagnostyczne.php. Co wypiszą poniższe instrukcje zapisane w tym pliku?
echo __DIR__;
echo dirname(__DIR__);
echo basename(__FILE__, '.php');
6. Co zostanie wyświetlone?
$plik = 'raport.2026.csv';
echo pathinfo($plik, PATHINFO_EXTENSION) . " | " . pathinfo($plik, PATHINFO_FILENAME);
7. Katalog data/ zawiera plik zadania.php i podkatalog archiwum. Ile wyniesie count(scandir(__DIR__ . '/data')) (skrypt leży w katalogu projektu)? Wymień wszystkie elementy.
8. Uzupełnij kod tak, aby katalog logs był tworzony tylko wtedy, gdy jeszcze nie istnieje:
$katalog = __DIR__ . '/logs';
if (________________) {
________________;
}
9. Katalog a nie istnieje. Co się stanie po wykonaniu mkdir(__DIR__ . '/a/b/c');? Jak poprawić wywołanie?
10. Plik includes/header.php ma dołączyć config/config.php. Która wersja jest poprawna? Co jest nie tak z pozostałymi?
a) require_once 'config/config.php';
b) require_once '../config/config.php';
c) require_once __DIR__ . '/../config/config.php';
d) require_once __DIR__ . 'config/config.php';
11. Dlaczego rmdir(__DIR__ . '/data'); w katalogu projektu TaskQuest nie usunie katalogu data?
12. Co jest niebezpieczne w tym kodzie? Jak to poprawić?
include 'pages/' . $_GET['strona'] . '.php';
13. Napisz funkcję czyKatalogIstnieje(string $sciezkaWzgledna): bool do pliku includes/funkcje_diagnostyczne.php — analogicznie do czyPlikIstnieje(). Dla 'data' ma zwrócić true, dla 'index.php' — false.
14. Dlaczego w <link rel="stylesheet" href="..."> nie wpisujemy ścieżki z __DIR__? Co dostałaby wtedy przeglądarka?
Odpowiedzi
- Dwa ostrzeżenia od
include, potemA, potem ostrzeżenie i błąd krytycznyFailed opening required 'brak.php'.Bnie zostanie wyświetlone. 1— kod za.phpwykonuje się w miejscurequirei nadpisuje zmienną.- Linia 3:
Cannot redeclare .... Linia 2 jest pomijana dzięki_once, ale zwykłyrequirenie sprawdza, czy plik był już dołączony — próbuje zadeklarować funkcję drugi raz. - Funkcja jest wywoływana, zanim plik z jej definicją został dołączony →
Call to undefined function obliczPoziom(). Poprawka: przenieśćrequire_oncedlafunkcje_grywalizacji.phpnad wywołanie. /var/www/taskquest/includes,/var/www/taskquest,funkcje_diagnostyczne.csv | raport.2026— rozszerzeniem jest tylko część po ostatniej kropce.4:.,..,archiwum,zadania.php.scandir()zwraca także katalogi, nie tylko pliki.!is_dir($katalog)orazmkdir($katalog).- Ostrzeżenie „No such file or directory”, funkcja zwraca
falsei nic nie tworzy. Poprawnie:mkdir(__DIR__ . '/a/b/c', 0755, true);. - Poprawna jest c. a) i b) to ścieżki względne liczone od katalogu uruchomionego skryptu, a nie od
header.php— gdyheader.phpjest dołączany zindex.php,'../config/config.php'wskazuje poza projekt. d) brakuje/po__DIR__, powstaje.../includesconfig/config.php. rmdir()usuwa tylko pusty katalog, a wdata/jestzadania.php. Funkcja zwrócifalsei wyświetli ostrzeżenie „Directory not empty”.- Użytkownik może w adresie podać np.
../../config/configi dołączyć dowolny plik (Path Traversal). Poprawka: lista dozwolonych stron i sprawdzenie, np.:$dozwolone = ['home', 'about', 'contact']; $strona = $_GET['strona'] ?? 'home'; if (in_array($strona, $dozwolone)) { include __DIR__ . '/pages/' . $strona . '.php'; } function czyKatalogIstnieje(string $sciezkaWzgledna): bool { return is_dir(dirname(__DIR__) . '/' . $sciezkaWzgledna); }hrefto adres, który pobiera przeglądarka (liczony od adresu strony, np.http://localhost/taskquest/).__DIR__to ścieżka na dysku serwera (np./var/www/taskquestalboC:\xampp\htdocs\taskquest) — przeglądarka nie ma do niej dostępu, więc styl by się nie wczytał. Dlatego piszemy np.href="styl.css"lubhref="assets/css/style.css".
