Skip to content

Wzorcowa struktura miniprojektu

This content is not available in your language yet.

Ta strona pokazuje jak zorganizować pliki i katalogi w miniprojekcie. Struktura jest prostsza niż w projekcie semestralnym, ale nadal wymaga logicznego podziału.


  • Directoryminiprojekt/
    • index.php - główny plik aplikacji
    • README.md - dokumentacja projektu
    • Directorydata/
      • dane.json - plik z danymi (lub .txt/.xml)
    • Directorycss/
      • style.css - style CSS
    • Directoryjs/
      • app.js - skrypty JavaScript

  1. Formularz HTML - z odpowiednimi typami pól (text, email, number, select, etc.)

  2. Walidacja PHP - sprawdzenie wszystkich wymaganych pól po stronie serwera

  3. Minimum 1 walidacja JS - poprawa UX (np. sprawdzenie przed wysłaniem)

  4. Zapis danych - do pliku JSON, TXT lub XML

  5. Odczyt i prezentacja - wyświetlenie zapisanych danych

  6. Obsługa błędów - komunikaty dla użytkownika

  7. Dokumentacja README.md - instrukcja uruchomienia i opis


<?php
// Konfiguracja
$dataFile = 'data/dane.json';
// Funkcje pomocnicze
function loadData($file) {
if (!file_exists($file)) {
return [];
}
$json = file_get_contents($file);
return json_decode($json, true) ?? [];
}
function saveData($file, $data) {
file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
// Obsługa formularza
$errors = [];
$success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Walidacja
$nazwa = trim($_POST['nazwa'] ?? '');
if (empty($nazwa)) {
$errors[] = 'Pole nazwa jest wymagane';
}
// Zapis jeśli brak błędów
if (empty($errors)) {
$data = loadData($dataFile);
$data[] = [
'nazwa' => $nazwa,
'data' => date('Y-m-d H:i:s')
];
saveData($dataFile, $data);
$success = 'Dane zostały zapisane!';
}
}
// Odczyt danych do wyświetlenia
$items = loadData($dataFile);
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Miniprojekt</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<main>
<h1>Moja aplikacja</h1>
<?php if ($success): ?>
<p class="success"><?= htmlspecialchars($success) ?></p>
<?php endif; ?>
<?php if (!empty($errors)): ?>
<ul class="errors">
<?php foreach ($errors as $error): ?>
<li><?= htmlspecialchars($error) ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<form method="post" id="mainForm">
<label for="nazwa">Nazwa:</label>
<input type="text" id="nazwa" name="nazwa" required>
<button type="submit">Zapisz</button>
</form>
<h2>Zapisane dane</h2>
<?php if (empty($items)): ?>
<p>Brak danych.</p>
<?php else: ?>
<ul>
<?php foreach ($items as $item): ?>
<li><?= htmlspecialchars($item['nazwa']) ?> (<?= htmlspecialchars($item['data']) ?>)</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</main>
<script src="js/app.js"></script>
</body>
</html>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, sans-serif;
line-height: 1.6;
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
h1, h2 {
margin-bottom: 1rem;
}
form {
margin-bottom: 2rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: bold;
}
input, select, textarea {
width: 100%;
padding: 0.5rem;
margin-bottom: 1rem;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
padding: 0.5rem 1rem;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
.success {
padding: 1rem;
background-color: #d4edda;
color: #155724;
border-radius: 4px;
margin-bottom: 1rem;
}
.errors {
padding: 1rem;
background-color: #f8d7da;
color: #721c24;
border-radius: 4px;
margin-bottom: 1rem;
list-style-position: inside;
}
ul {
list-style-position: inside;
}
li {
padding: 0.25rem 0;
}
// Walidacja formularza przed wysłaniem
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('mainForm');
if (form) {
form.addEventListener('submit', function(e) {
const nazwa = document.getElementById('nazwa');
if (nazwa && nazwa.value.trim() === '') {
e.preventDefault();
alert('Proszę wypełnić pole nazwa!');
nazwa.focus();
}
});
}
});

  • Directoryminiprojekt/
    • index.php - router / punkt wejścia
    • README.md - dokumentacja
    • Directorylib/
      • config.php - konfiguracja (ścieżki, stałe)
      • functions.php - funkcje pomocnicze
      • validation.php - funkcje walidacji
      • storage.php - zapis/odczyt danych
    • Directoryviews/
      • header.php - nagłówek HTML
      • footer.php - stopka HTML
      • form.php - widok formularza
      • list.php - widok listy danych
    • Directorydata/
      • dane.json - główne dane
      • historia.json - log operacji (opcjonalnie)
    • Directorycss/
      • style.css
    • Directoryjs/
      • app.js

lib/config.php:

<?php
define('DATA_FILE', __DIR__ . '/../data/dane.json');
define('HISTORY_FILE', __DIR__ . '/../data/historia.json');

lib/functions.php:

<?php
require_once 'config.php';
function loadData() {
if (!file_exists(DATA_FILE)) {
return [];
}
return json_decode(file_get_contents(DATA_FILE), true) ?? [];
}
function saveData($data) {
file_put_contents(DATA_FILE, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
function addToHistory($action, $details) {
$history = [];
if (file_exists(HISTORY_FILE)) {
$history = json_decode(file_get_contents(HISTORY_FILE), true) ?? [];
}
$history[] = [
'action' => $action,
'details' => $details,
'timestamp' => date('Y-m-d H:i:s')
];
file_put_contents(HISTORY_FILE, json_encode($history, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}

lib/validation.php:

<?php
function validateRequired($value, $fieldName) {
if (empty(trim($value))) {
return "Pole {$fieldName} jest wymagane";
}
return null;
}
function validateEmail($email) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return "Nieprawidłowy format email";
}
return null;
}
function validateNumericRange($value, $min, $max, $fieldName) {
if (!is_numeric($value) || $value < $min || $value > $max) {
return "Pole {$fieldName} musi być liczbą od {$min} do {$max}";
}
return null;
}

# Nazwa projektu
Krótki opis aplikacji (3-6 zdań) - co robi, jaki problem rozwiązuje.
## Jak uruchomić
1. Rozpakuj archiwum do katalogu XAMPP htdocs
2. Uruchom XAMPP (Apache)
3. Otwórz w przeglądarce: http://localhost/miniprojekt/
## Wymagania
- PHP 8.x
- XAMPP / WAMP / MAMP
## Wariant
Wariant A - podstawowy
# Nazwa projektu
Krótki opis aplikacji (3-6 zdań) - co robi, jaki problem rozwiązuje.
## Jak uruchomić
1. Rozpakuj archiwum do katalogu XAMPP htdocs
2. Uruchom XAMPP (Apache)
3. Otwórz w przeglądarce: http://localhost/miniprojekt/
## Wymagania
- PHP 8.x
- XAMPP / WAMP / MAMP
## Wariant
Wariant B - rozszerzony
## Struktura katalogów
- index.php - główny punkt wejścia
- lib/ - funkcje pomocnicze i konfiguracja
- views/ - szablony HTML
- data/ - pliki z danymi (JSON)
- css/ - style
- js/ - skrypty
## Format danych
Dane są przechowywane w formacie JSON (nazwa, data jako YYYY-MM-DD HH:MM:SS)
## Walidacja
- Pola wymagane: nazwa
- Format: email sprawdzany przez filter_var()
- Zabezpieczenia: htmlspecialchars() przy wyświetlaniu
## Znane ograniczenia
- Brak obsługi usuwania danych
- TODO: dodać sortowanie listy

Obowiązkowe zabezpieczenia

  • htmlspecialchars() - przy każdym wyświetleniu danych użytkownika
  • Walidacja wartości - sprawdzaj dozwolone wartości dla select/radio
  • Obsługa błędów - graceful handling gdy brak pliku danych
  • Brak eval/include z danymi użytkownika - nigdy nie używaj danych użytkownika w ścieżkach
// Dobrze
echo htmlspecialchars($userInput);
// Źle - podatność XSS!
echo $userInput;
// Dobrze - walidacja dozwolonych wartości
$allowedStatuses = ['aktywny', 'nieaktywny', 'oczekujący'];
if (!in_array($status, $allowedStatuses)) {
$errors[] = 'Nieprawidłowy status';
}
// Źle - brak walidacji
$status = $_POST['status']; // użytkownik może wpisać cokolwiek

Wariant A

  • 1 plik PHP (index.php)
  • Podstawowa struktura katalogów
  • README z minimum info

Wariant B/C

  • Podział na pliki (lib/, views/)
  • Historia operacji
  • Rozbudowane README
  • Filtrowanie/sortowanie
Zacznij od Wariantu A, rozbuduj do B/C gdy działa podstawa!