INF.03 · Aplikacje internetowe PHP

Projekt końcowy: kompletny serwis filmowy

Połącz elementy w jeden działający serwis.

  • PHP8.3
  • mysqli · MariaDB
  • Praktyka i diagnoza

Po tej lekcji: Uruchomisz i przetestujesz cały projekt, wyjaśniając drogę danych i własne decyzje.

Zrozum mechanizm

Projekt łączy formularze, funkcje, bazę, sesje i autoryzację. Katalog jest publiczny, panel prywatny, a rekordy filmów mają właścicieli. Użytkownik może zarejestrować się, zalogować, utworzyć własny film, zmienić go, dodać publiczne filmy do koszyka i zapisać symulowane zamówienie. Każda z tych operacji ma osobny warunek dostępu i sposób sprawdzenia.

Oddziel src od public. Konfiguracja i funkcje nie są plikami do pobierania przez HTTP. Nie oddawaj config.local.php, haseł ani sesji. Plik schema.sql zapewnia odtwarzalne środowisko z fikcyjnymi danymi. Testy powinny obejmować sukces i odmowę: brak logowania, zły format, cudzy rekord, brak tokenu, powtórzenie i awarię zapisu.

Wersja szkolna pokazuje mechanizmy, nie jest gotową usługą publiczną. Przed realną publikacją potrzebne są m.in. aktualizacje, HTTPS, potwierdzenie i odzyskanie konta, dopracowane ograniczanie nadużyć, monitoring oraz procedura kopii i odtwarzania. To osobne decyzje, których nie zastąpi atrakcyjny widok formularza.

Jak uruchamiać krótkie przykłady?

Każdy krótki przykład oznaczony własnym plikiem uruchamiaj osobno, nie doklejaj wszystkich definicji do jednego pliku. W edytorze zapisz go jako .php, a w terminalu tego folderu wpisz np. php for.php. Wtedy pokazane końce wierszy PHP_EOL są widoczne. W odpowiedzi HTML znak końca wiersza sam nie robi nowej linii na ekranie: użyj <br> albo znacznika <pre>. Wyświetl źródło pozwala zobaczyć tekst odpowiedzi.

Przykłady opisane jako fragment projektu wymagają paczki KINO / LAB i wcześniejszej konfiguracji. Nie uruchamiaj ich jako samodzielnych endpointów. Pełny plik niżej pokazuje kontekst. Funkcje h(), pole() i wymagajPost() są nasze; ich definicje wczytuje bootstrap.php.

Jak odczytać przepływ całej aplikacji?

Nie zaczynaj od przepisywania całego pliku. Wybierz jedną czynność: dodanie filmu. Prześledź, gdzie powstaje każda wartość. Ten wycinek pokazuje kolejność wywołań wewnątrz poprawnego POST w dodaj.php.

Podstawowa forma: formularz → dane → walidacja → zapytanie → wynik → odpowiedź

project-data.phpphp
<?php
$idUzytkownika = wymagajLogowania();
wymagajPost();
[$tytul, $rok, $gatunek, $cena] = daneFilmu($_POST);
// Dalej: prepare, bind_param, execute (pełny dodaj.php).
przekieruj('/panel.php');
  1. wymagajLogowania zwraca ID z sesji lub przerywa dostęp. wymagajPost sprawdza metodę i CSRF. To nasze funkcje, zdefiniowane w src/funkcje.php.
  2. daneFilmu przyjmuje tablicę formularza i zwraca cztery sprawdzone wartości. Nawiasy po lewej rozpakowują wynik, nie tworzą nowego formularza.
  3. Zapytanie zapisuje dane z ID właściciela. przekieruj wysyła 303 i exit; dlatego instrukcje po nim nie są dalszym etapem odpowiedzi.
  4. Zacznij od działających podstaw bez bazy. Dopiero po konfiguracji MariaDB przejdź katalog → rejestracja → logowanie → własny CRUD → koszyk → zamówienie → wylogowanie.

Jak czytać pozostałe zapisy w pełnym przykładzie

ZapisZnaczeniePrzykład użycia
pole($dane, $nazwa)Nasza funkcja: pobiera wskazany klucz tylko wtedy, gdy ma wartość string; inaczej oddaje pusty napis. isset sprawdza obecność i brak null, is_string typ.pole($_POST, "email")
h($tekst)Nasza funkcja kodująca tekst/atrybut HTML przez htmlspecialchars; nie wykonuje walidacji zakresu ani zabezpieczenia SQL.h($film["tytul"])
tokenFormularza()Nasza funkcja zwraca HTML ukrytego pola csrf z wartością sesji zakodowaną przez h.<?= tokenFormularza() ?>
naglowek($tytul), stopka()Nasze funkcje wypisujące wspólny HTML strony. void oznacza brak zwracanego wyniku do użycia w wyrażeniu.naglowek("Katalog");
pieniadze($grosze)Nasza funkcja formatuje grosze jako złote. number_format przyjmuje liczbę, liczbę miejsc po przecinku, separator dziesiętny i separator grup. Wynik to string.number_format(12.99, 2, ",", " ")
dirname(__DIR__)dirname daje katalog nadrzędny; __DIR__ to katalog bieżącego pliku. W public prowadzi to do katalogu projektu, a dopisana ścieżka wskazuje src.dirname(__DIR__)."/src/bootstrap.php"
-> oraz ::-> wybiera właściwość lub metodę obiektu; :: odnosi się do elementu statycznego klasy. To nie jest zapis dostępu do tablicy.$zapytanie->execute();

Sprawdź w działaniu

Połącz elementy w jeden działający serwis — sprawdź w modelu

Model w przeglądarce wyjaśnia mechanizm na fikcyjnych danych. Nie wykonuje PHP ani zapytań SQL i nie tworzy kont. Prawdziwe skrypty uruchomisz lokalnie z paczki.

Ta interakcja wymaga włączonego JavaScriptu.

Przewidź rezultat, wykonaj próbę i wyjaśnij, która decyzja po stronie serwera decyduje o wyniku.

Uruchom krok po kroku

Zacznij od START.txt i importu schematu. Uruchom katalog. Przejdź całą ścieżkę jako gość i dwa fikcyjne konta. Dodaj jedną własną funkcję, np. filtr gatunku, pozostawiając parametry SQL i kontrolę dostępu. Na końcu odtwórz projekt w nowym folderze i czystej bazie ćwiczeniowej z własnego kompletu plików.

Plik wzorcowy: schema.sql

schema.sqlsql
CREATE DATABASE IF NOT EXISTS serwis_filmowy CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE serwis_filmowy;
CREATE TABLE IF NOT EXISTS Uzytkownicy (
 id INT AUTO_INCREMENT PRIMARY KEY,
 email VARCHAR(254) NOT NULL UNIQUE,
 haslo_hash VARCHAR(255) NOT NULL
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS Filmy (
 id INT AUTO_INCREMENT PRIMARY KEY,
 tytul VARCHAR(100) NOT NULL,
 rok SMALLINT NOT NULL,
 gatunek VARCHAR(40) NOT NULL,
 cena_grosze INT NOT NULL CHECK (cena_grosze >= 0),
 wlasciciel_id INT NULL,
 FOREIGN KEY (wlasciciel_id) REFERENCES Uzytkownicy(id)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS Zamowienia (
 id INT AUTO_INCREMENT PRIMARY KEY,
 uzytkownik_id INT NOT NULL,
 klucz CHAR(64) NOT NULL UNIQUE,
 suma_grosze INT NOT NULL,
 status VARCHAR(20) NOT NULL DEFAULT 'symulacja',
 utworzono TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
 FOREIGN KEY (uzytkownik_id) REFERENCES Uzytkownicy(id)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS Pozycje (
 zamowienie_id INT NOT NULL,
 film_id INT NOT NULL,
 ilosc SMALLINT NOT NULL CHECK (ilosc BETWEEN 1 AND 10),
 cena_grosze INT NOT NULL,
 PRIMARY KEY (zamowienie_id, film_id),
 FOREIGN KEY (zamowienie_id) REFERENCES Zamowienia(id),
 FOREIGN KEY (film_id) REFERENCES Filmy(id)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS ProbyLogowania (
 klucz CHAR(64) PRIMARY KEY,
 liczba INT NOT NULL DEFAULT 0,
 od_kiedy BIGINT NOT NULL
) ENGINE=InnoDB;
INSERT INTO Filmy (id,tytul,rok,gatunek,cena_grosze) VALUES
 (1,'Podróż',2024,'przygodowy',1299), (2,'Kosmos',2023,'science fiction',1599),
 (3,'Dom',2025,'familijny',999)
ON DUPLICATE KEY UPDATE id=id;

Punkt wejścia kompletnego projektu

public/index.phpphp
<?php
require_once dirname(__DIR__).'/src/db.php';
$szukaj = trim(pole($_GET, 'szukaj'));
if (mb_strlen($szukaj) > 100) {
    http_response_code(422);
    exit('Wpisz do 100 znaków.');
}
$wzorzec = '%'.$szukaj.'%';
$zapytanie = $polaczenie->prepare('SELECT id, tytul, rok, gatunek, cena_grosze FROM Filmy WHERE tytul LIKE ? ORDER BY rok DESC, id');
$zapytanie->bind_param('s', $wzorzec);
$zapytanie->execute();
$filmy = $zapytanie->get_result()->fetch_all(MYSQLI_ASSOC);
naglowek('Wybierz historię na dziś.');
?>
<form method="get" action="/index.php">
    <label for="szukaj">Szukaj tytułu</label>
    <input id="szukaj" name="szukaj" maxlength="100" value="<?= h($szukaj) ?>">
    <button>Szukaj</button>
</form>
<p><a href="/rejestracja.php">Utwórz konto</a> · <a href="/logowanie.php">Zaloguj się</a></p>
<div class="karty">
<?php foreach ($filmy as $film) { ?>
    <article><span><?= (int) $film['rok'] ?> / <?= h($film['gatunek']) ?></span>
        <h2><?= h($film['tytul']) ?></h2><p><?= pieniadze((int) $film['cena_grosze']) ?></p>
        <form action="/koszyk.php" method="post">
            <?= tokenFormularza() ?>
            <input type="hidden" name="id" value="<?= (int) $film['id'] ?>">
            <button name="akcja" value="dodaj">Dodaj do koszyka</button>
        </form>
    </article>
<?php } ?>
</div>
<?php
if (! $filmy) {
    echo '<p>Brak filmów spełniających warunek.</p>';
}
stopka();

Pobierz i odtwórz projekt

W „Źródłach i materiałach” na końcu pobierz KINO / LAB.zip. Rozpakuj całość; instrukcja START.txt opisuje konfigurację. Pierwsze 6 przykładów w public/podstawy działa bez bazy. Dalsze wymagają lokalnej MariaDB 11.4, importu schema.sql i lokalnego konta bazy z minimalnymi prawami. Gotowa paczka pokazuje całość, ale w tej lekcji wykonaj opisane kroki i własne zadanie.

Ćwiczenie z pomocą · samodzielna praca

Twoja próba — podpowiedzi otwieraj po kolei

Rozbuduj serwis o filtr gatunku i stronę szczegółów filmu. Oddaj ZIP bez konfiguracji lokalnej oraz kartę co najmniej 10 prób.

Oczekiwany rezultat
Projekt odtwarza się z plików i schema.sql; kontrole dostępu nadal działają; karta testów podaje wyniki.
Potrzebujesz pomocy? Rozwiązuj krok po kroku.
Krok 1 — pokaż podpowiedź

Odszukaj wskazany plik i zapisz przewidywany rezultat przed zmianą.

Krok 2 — pokaż podpowiedź

Zmień jedną rzecz. Sprawdź metodę żądania, dane i odpowiedź lub wynik programu.

Krok 3 — pokaż podpowiedź

Wykonaj próbę graniczną lub błędną. Porównaj także stan bazy lub sesji, nie tylko komunikat.

Otwórz tylko tyle podpowiedzi, ile potrzebujesz. Po każdym kroku wróć do zadania i sprawdź wynik.

Podsumowanie

Co warto zapamiętać

  1. Uruchomisz i przetestujesz cały projekt, wyjaśniając drogę danych i własne decyzje.
  2. Potrafisz wskazać w kodzie przyczynę wyniku i zaplanować próbę błędnych danych.
  3. Nie oddajesz haseł, pliku config.local.php ani wartości cookie.

Dalej na swoim koncie

Rozgrzewki, quiz i zadania są w biurku ucznia.

Teoria jest otwarta dla wszystkich. Żeby rozwiązywać zadania, zapisywać szkice i widzieć swój postęp, zaloguj się.

Zaloguj się →
Źródła i dalsza nauka