09. Мини-проект: система заметок на PHP + MySQL + ООП

Введение

В этом уроке мы создадим простое, но полноценное приложение — Систему заметок. Пользователь сможет зарегистрироваться, войти и создавать свои заметки. Всё это — с использованием ООП, PDO и структуры MVC.
Это уже не просто учебный пример — это мини-веб-приложение, как основа для будущего проекта или портфолио!

Что будет в проекте

  • Регистрация и вход пользователя
  • Создание, редактирование и удаление заметок (CRUD)
  • Использование сессий и защита от XSS
  • Структура MVC (Model-View-Controller)

Структура проекта


notes_app/
├── core/
│   ├── Database.php
│   └── Auth.php
├── models/
│   └── NoteModel.php
│   └── UserModel.php
├── controllers/
│   ├── NoteController.php
│   └── UserController.php
├── views/
│   ├── notes_list.php
│   ├── note_form.php
│   ├── login.php
│   └── register.php
└── index.php

Создание таблиц в MySQL


CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  email VARCHAR(150) UNIQUE NOT NULL,
  password VARCHAR(255) NOT NULL
);

CREATE TABLE notes ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, title VARCHAR(200) NOT NULL, text TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE );

Класс Database


<?php
class Database {
  private $pdo;

public function __construct() { $this->pdo = new PDO('mysql:host=localhost;dbname=notes_app;charset=utf8mb4','root',''); $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); }

public function query($sql, $params = []) { $stmt = $this->pdo->prepare($sql); $stmt->execute($params); return $stmt; } } ?>

Модель пользователя (UserModel)


<?php
class UserModel extends Database {
  public function register($name, $email, $password) {
    $hash = password_hash($password, PASSWORD_DEFAULT);
    $this->query('INSERT INTO users (name,email,password) VALUES (?,?,?)', [$name,$email,$hash]);
  }

public function findByEmail($email) { return $this->query('SELECT * FROM users WHERE email = ?', [$email])->fetch(); } } ?>

Авторизация и сессии (Auth.php)


<?php
class Auth {
  public static function start() {
    if (session_status() === PHP_SESSION_NONE) session_start();
  }

public static function login($user) { $_SESSION['user'] = $user; }

public static function logout() { session_destroy(); }

public static function user() { return $_SESSION['user'] ?? null; }

public static function check() { return isset($_SESSION['user']); } } ?>

Контроллер пользователя (UserController)


<?php
require_once 'models/UserModel.php';
require_once 'core/Auth.php';

class UserController { public function register() { if ($_SERVER['REQUEST_METHOD'] === 'POST') { $name = $_POST['name']; $email = $_POST['email']; $pass = $_POST['password'];

$model = new UserModel(); $model->register($name,$email,$pass);

header('Location: index.php?action=login'); exit; } include 'views/register.php'; }

public function login() { Auth::start(); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $email = $_POST['email']; $pass = $_POST['password']; $model = new UserModel(); $user = $model->findByEmail($email); if ($user && password_verify($pass, $user['password'])) { Auth::login($user); header('Location: index.php?action=notes'); exit; } else { echo '<p style="color:red">Неверные данные</p>'; } } include 'views/login.php'; }

public function logout() { Auth::logout(); header('Location: index.php?action=login'); } } ?>

Модель заметок (NoteModel)


<?php
class NoteModel extends Database {
  public function getAll($uid) {
    return $this->query('SELECT * FROM notes WHERE user_id = ? ORDER BY id DESC', [$uid])->fetchAll();
  }

public function find($id, $uid) { return $this->query('SELECT * FROM notes WHERE id = ? AND user_id = ?', [$id,$uid])->fetch(); }

public function create($title, $text, $uid) { $this->query('INSERT INTO notes (title,text,user_id) VALUES (?,?,?)', [$title,$text,$uid]); }

public function update($id, $title, $text, $uid) { $this->query('UPDATE notes SET title=?, text=? WHERE id=? AND user_id=?', [$title,$text,$id,$uid]); }

public function delete($id, $uid) { $this->query('DELETE FROM notes WHERE id=? AND user_id=?', [$id,$uid]); } } ?>

Контроллер заметок (NoteController)


<?php
require_once 'models/NoteModel.php';
require_once 'core/Auth.php';

class NoteController { public function index() { Auth::start(); $uid = Auth::user()['id'] ?? null; if (!$uid) { header('Location: index.php?action=login'); exit; }

$model = new NoteModel(); $notes = $model->getAll($uid); include 'views/notes_list.php'; }

public function create() { Auth::start(); $uid = Auth::user()['id'] ?? null; if ($_SERVER['REQUEST_METHOD'] === 'POST') { (new NoteModel())->create($_POST['title'], $_POST['text'], $uid); header('Location: index.php?action=notes'); exit; } include 'views/note_form.php'; } } ?>

Виды (Views)

views/login.php

<h2>Вход</h2>
<form method='post'>
  <input name='email' placeholder='Email'><br>
  <input name='password' type='password' placeholder='Пароль'><br>
  <button>Войти</button>
</form>
<a href='index.php?action=register'>Регистрация</a>

views/register.php


<h2>Регистрация</h2>
<form method='post'>
  <input name='name' placeholder='Имя'><br>
  <input name='email' placeholder='Email'><br>
  <input name='password' type='password' placeholder='Пароль'><br>
  <button>Создать аккаунт</button>
</form>
<a href='index.php?action=login'>Уже есть аккаунт?</a>

views/notes_list.php


<h2>Мои заметки</h2>
<a href='index.php?action=create_note'>+ Новая заметка</a>
<hr>
<?php foreach ($notes as $n): ?>
  <div style='border:1px solid #ccc; padding:10px; margin:10px 0;'>
    <h3><?= htmlspecialchars($n['title']) ?></h3>
    <p><?= nl2br(htmlspecialchars($n['text'])) ?></p>
    <small>Создано: <?= htmlspecialchars($n['created_at']) ?></small>
  </div>
<?php endforeach; ?>

views/note_form.php


<h2>Новая заметка</h2>
<form method='post'>
  <input name='title' placeholder='Заголовок'><br>
  <textarea name='text' placeholder='Текст заметки'></textarea><br>
  <button>Сохранить</button>
</form>
<a href='index.php?action=notes'>Назад</a>

Файл index.php


<?php
require_once 'controllers/UserController.php';
require_once 'controllers/NoteController.php';

action_router: $action = $_GET['action'] ?? 'login'; $userController = new UserController(); $noteController = new NoteController();

switch ($action) { case 'register': $userController->register(); break; case 'login': $userController->login(); break; case 'logout': $userController->logout(); break; case 'notes': $noteController->index(); break; case 'create_note': $noteController->create(); break; default: echo 'Страница не найдена'; } ?>

Проверяем результат

1️⃣ Зарегистрируйтесь через форму регистрации. 2️⃣ Войдите под своими данными. 3️⃣ Добавьте заметку — она появится в списке. 4️⃣ Данные сохраняются в MySQL, каждая заметка связана с пользователем.

Защита и советы

  • Используйте htmlspecialchars() при выводе текста заметок
  • Проверяйте сессию перед выполнением действий
  • Пароли храните только в виде хэшей
  • Добавьте проверку прав (uid == author_id)

Итоги урока

Вы создали:
  • Рабочую систему с регистрацией и авторизацией
  • CRUD-операции для заметок
  • Простую архитектуру MVC
  • Классы для работы с БД, пользователями и заметками
  • Мини-приложение, которое можно развивать дальше!