Этот урок ориентирован на тех, кто совсем немного знаком с PHP и Объектно-Ориентированным Программированием (ООП) и хотят создать простое веб приложение.
Для более удобного восприятия материала урок состоит из 3-х частей.
Обзор серии уроков
Нам необходимо изучить очень много материала. Вот план:
Часть №1 - Создаем проект и создаем первый класс
- создаем набросок проекта
- создаем файлы и папки
- создаем класс для операций с базой данной: DB.class.php
Часть №2 - Доделываем серверную чаcть
- Создаем класс для пользователей (User)
- Создаем класс UserTools
- Регистрация \ Логин \ Выход
Часть №3 - Создаем внешний интерфейс
- Формы
- Обработка форм
- Отображение информации сессий
Начинаем наш проект!
Создаем план действий
Всегда важно представлять конечный результат. Прежде чем начать с кодом необходимо задать цели, представить весь проект и решить какой будет структура папок и какие файлы Вам понадобятся. Цель данного проекта очень простая: создать простое приложение на PHP с возможностью регистрации пользователей, входа и выхода, а также с возможностью пользователям менять собственные настройки.
Структура Файлов и Папок
ООП PHP программирование использует классы и объекты для выполнения необходимых для приложения операций. При планировке Вам необходимо подумать о том, какие классы Вам понадобятся. Для данного проекта мы создадим 3 класса. Первый класс - User (будет содержать информацию о пользователе с функцией простого сохранения save()), второй - UserTools (будет содержать функции, которые необходимы пользователям, такие как login(), logout() и другие...), третий - класс БД (он будет выполнять роль связующего звена - подсоединение к БД, внесение изменений, вставка новых рядов, и многое другое).
Кроме классов, мы также будем использовать файл с названием global.inc.php. Этот файл будет вызываться с каждой страницы и выполнять обычные операции, которые нам понадобятся. К примеру, в этом файле мы будем выполнять подключение к БД на каждой странице.
Другие файлы - это страницы для пользователей: index.php, register.php, login.php, logout.php, settings.php и welcome.php.
Общая структура у нас будет выглядеть так:
Создание Базы Данных и таблицы users
На Вашем сервере должен быть установлен MySQL. Для начала необходимо создать новую базу данных для Вашего приложения. В этой БД создайте таблицу users, которую мы будем использовать для этого урока. Можете использовать следующий код SQL:
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(50) NOT NULL,
`email` varchar(50) NOT NULL,
`join_date` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
Первичным ключом у нас выступает поле “id”, и именно его мы будем использовать для идентификации наших пользователей.
Уникальным полем у нас также будет “username”. Другие необходимые поля “password”, “email”, и “join_date”.
Создаем класс DB.class.php
Цель данного класса очень проста: как можно больше уменьшить использование SQL при обращении к БД, а также организовать данные в удобный для нас формат.
Ниже приведен код:
<?php
//DB.class.php
class DB {
protected $db_name = 'yourdatabasename';
protected $db_user = 'databaseusername';
protected $db_pass = 'databasepassword';
protected $db_host = 'localhost';
// Открывает соединение к БД. Убедитесь, что
// эта функция вызывается на каждой странцице
public function connect() {
$connection = mysql_connect($this->db_host, $this->db_user, $this->db_pass);
mysql_select_db($this->db_name);
return true;
}
// Берет ряд mysql и возвращает ассоциативный массив, в котором
// названия колонок являются ключами массива. Если singleRow - true,
// тогда выводится только один ряд
public function processRowSet($rowSet, $singleRow=false)
{
$resultArray = array();
while($row = mysql_fetch_assoc($rowSet))
{
array_push($resultArray, $row);
}
if($singleRow === true)
return $resultArray[0];
return $resultArray;
}
//Выбирает ряды из БД
//Выводит полный ряд или ряды из $table используя $where
public function select($table, $where) {
$sql = "SELECT * FROM $table WHERE $where";
$result = mysql_query($sql);
if(mysql_num_rows($result) == 1)
return $this->processRowSet($result, true);
return $this->processRowSet($result);
}
//Вносит изменения в БД
public function update($data, $table, $where) {
foreach ($data as $column => $value) {
$sql = "UPDATE $table SET $column = $value WHERE $where";
mysql_query($sql) or die(mysql_error());
}
return true;
}
//Вставляет новый ряд в таблицу
public function insert($data, $table) {
$columns = "";
$values = "";
foreach ($data as $column => $value) {
$columns .= ($columns == "") ? "" : ", ";
$columns .= $column;
$values .= ($values == "") ? "" : ", ";
$values .= $value;
}
$sql = "insert into $table ($columns) values ($values)";
mysql_query($sql) or die(mysql_error());
//Выводит ID пользователя в БД.
return mysql_insert_id();
}
}
?>
Объяснение кода
После создания класса Вы видите 4 переменные: $db_name, $db_user, $db_pass, и $db_host. В них необходимо внести данные для подключения к БД. $db_host обычно localhost. Перед этими переменными указано "protected" - это означает, что они будут не доступны вне этого класса. Внутри же класса их можно выводить используя $this->db_name, $this->db_user, и т.д.
Первая функция называется connect(). Эта функция содержит защищенные значения для соединения с БД. Это соединение будет открыто для использования в любом месте текущей страницы (не только внутри класса).
Вот пример использования этой функции вне класса:
$db = new DB();
$db->connect();
Вторая функция называется processRowSet(). Цель данной функции - взять объект результата mysql и конвертировать его в ассоциативный массив, в котором ключами являются название колонок. Функция проходит по каждому ряду и функция mysql_fetch_assoc() преобразовывает каждый ряд в массив. Ряд далее передается массиву и возвращается с помощью функции.
Существует второй аргумент $singleRow, который содержит значение по умолчанию. Если значение true, выводится только один ряд вместо массива. Это очень полезно, если Вы ожидаете получить один результат (например, при выборе юзера из БД используя уникальный id).
Последние 3 функции выполняют простые фукнции MySQL: select, insert, update. Цель данных функций минимизировать количество SQL кода, который необходимо использовать где-либо в другом месте приложения. Каждая функция создает SQL запрос на основе переданного значения и выполняет этот запрос. В случае select(), результаты форматируются и выводятся. В случае update(), выводится true при успешном выполнении. В случае insert(), выводится id нового ряда.
Вот пример как Вы можете изменить данные пользователя в БД используя функцию update():
$db = new DB();
$data = array(
"username" => "'johndoe'",
"email" => "'johndoe@email.com'"
);
//Найти юзера с id = 3 в БД и внести изменения в ряд
//username - johndoe и e-mail - johndoe@email.com
$db->update($data, 'users', 'id = 3');
Во втором уроке мы напишем еще два класса и полностью закончим внутреннюю часть скрипта.
План
Цель серии уроков создать простое приложение, которое позволяет пользователям регистрироваться, входить, выходить и менять настройки. Класс, который будет содержать всю информации о пользователе будет называться User и он будет определен в файле User.class.php. Класс, который будет отвечать за вход\выход будет называться UserTools (UserTools.class.php).
Немного про именование классов
Правильным тоном является называть файлы с описанием класса таким же именем как и сам класс. Таким образом легко определить цель каждого файла в папке с классами.
Также обычно в конце названия файла класса добавляют .class или .inc. Таким образом мы четко определяем предназначение файла и можем с помощью .htaccess ограничить доступ к этим файлам.
Класс Пользователей (User.class.php)
Этот класс будет определять каждого пользователя. С ростом данного приложения определение "Пользователь" может существенно измениться. К счастью, ООП программирование позволяет легко добавлять дополнительные атрибуты пользователей.
Конструктор
В этом классе мы будем использовать конструктор - это функция, которая автоматически вызывается при создании очередной копии класса. Это позволяет нам автоматически публиковать некоторые атрибуты после создания проекта. В этом классе конструктор будет брать единственный аргумент: ассоциативный массив, который содержит один ряд из таблицы users нашей БД.
Код
<?php
//User.class.php
require_once 'DB.class.php';
class User {
public $id;
public $username;
public $hashedPassword;
public $email;
public $joinDate;
//Конструктор вызывается при создании нового объекта
//Takes an associative array with the DB row as an argument.
function __construct($data) {
$this->id = (isset($data['id'])) ? $data['id'] : "";
$this->username = (isset($data['username'])) ? $data['username'] : "";
$this->hashedPassword = (isset($data['password'])) ? $data['password'] : "";
$this->email = (isset($data['email'])) ? $data['email'] : "";
$this->joinDate = (isset($data['join_date'])) ? $data['join_date'] : "";
}
public function save($isNewUser = false) {
//create a new database object.
$db = new DB();
//if the user is already registered and we're
//just updating their info.
if(!$isNewUser) {
//set the data array
$data = array(
"username" => "'$this->username'",
"password" => "'$this->hashedPassword'",
"email" => "'$this->email'"
);
//update the row in the database
$db->update($data, 'users', 'id = '.$this->id);
}else {
//if the user is being registered for the first time.
$data = array(
"username" => "'$this->username'",
"password" => "'$this->hashedPassword'",
"email" => "'$this->email'",
"join_date" => "'".date("Y-m-d H:i:s",time())."'"
);
$this->id = $db->insert($data, 'users');
$this->joinDate = time();
}
return true;
}
}
?>
Объяснение
Первая часть кода, вне зоны класса, обеспечивает подключение класса в БД (поскольку в классе User есть функция, которая требует этот класс).
Вместо переменных класса “protected” (использовались в 1-м уроке) мы определяем их как “public”. Это означает, что любой код вне класса имеет доступ к этим переменным при работе с объектом User.
Конструктор берет массив, в котором колонки в таблице являются ключами. Мы задаем переменную класса используя $this->variablename. В примере данного класса, мы прежде всего проверяем существует ли значение определенного ключа. Если да, тогда мы приравниваем переменную класса к этому значению. В противном случае - пустая строка. Код использует краткую форму записи оборота if:
$value = (3 == 4) ? "A" : "B";
В данном примере мы проверяем равняется ли 3 четырем! Если да - тогда $value = “A”, нет - $value = “B”. В нашем примере результат $value = “B”.
Сохраняем Информацию о Пользователях в БД
Функция сохранения используется для внесения изменений в таблицу БД с текущими значениями в объекте User. Эта функция использует класс БД, который мы создали в первом уроке. Используя переменные класса, устанавливается массив $data. Если данные о пользователе сохраняются впервые, тогда $isNewUser передается как $true (по умолчанию false). Если $isNewUser = $true, тогда вызывается функция insert() класса DB. В противном случае вызывается функция update(). В обоих случаях информация от объекта user будет сохранена в БД.
Класс UserTools.class.php
Этот класс будет содержать функции, которые имеют отношение к пользователям: login(), logout(), checkUsernameExists() и get(). Но с расширением данного приложения, Вы можете добавить еще множество других.
Код
<?php
//UserTools.class.php
require_once 'User.class.php';
require_once 'DB.class.php';
class UserTools {
//Log the user in. First checks to see if the
//username and password match a row in the database.
//If it is successful, set the session variables
//and store the user object within.
public function login($username, $password)
{
$hashedPassword = md5($password);
$result = mysql_query("SELECT * FROM users WHERE username = '$username' AND
password = '$hashedPassword'");
if(mysql_num_rows($result) == 1)
{
$_SESSION["user"] = serialize(new User(mysql_fetch_assoc($result)));
$_SESSION["login_time"] = time();
$_SESSION["logged_in"] = 1;
return true;
}else{
return false;
}
}
//Log the user out. Destroy the session variables.
public function logout() {
unset($_SESSION['user']);
unset($_SESSION['login_time']);
unset($_SESSION['logged_in']);
session_destroy();
}
//Check to see if a username exists.
//This is called during registration to make sure all user names are unique.
public function checkUsernameExists($username) {
$result = mysql_query("select id from users where username='$username'");
if(mysql_num_rows($result) == 0)
{
return false;
}else{
return true;
}
}
//get a user
//returns a User object. Takes the users id as an input
public function get($id)
{
$db = new DB();
$result = $db->select('users', "id = $id");
return new User($result);
}
}
?>
Функция login()
Функция login() понятна по названию. Она берет аргументы пользователя $username и $password и проверяет их соответствие. Если все совпадает, создает объект User со всей информацией и сохраняет его в сессии. Обратите внимание, что мы только используем функцию PHP serialize(). Она создает сохраненный вариант объекта, который можно легко отменить с помощью unserialize(). Также время логина будет сохранено. Это может использоваться в дальнейшем для предоставления пользователям информации о длительности пребывания на сайте.
Вы также можете заметить, что мы выставляем $_SESSION['logged_in'] на 1. Это позволяет нам легко проверить на каждой странице залогинен ли пользователь. Достаточно проверить только эту переменную.
Функция logout()
Также простая функция. Функция PHP unset() очищает переменные в памяти, в то время как session_destroy() удалит сессию.
Функция checkUsernameExists()
Кто знает английский легко поймет функцию. Она просто запрашивает БД, использован ли подобный логин или нет.
Функция get()
Эта функция берет уникальный id пользователя и делает запрос к БД с помощью класса DB, а именно функции select(). Она возьмет ассоциативный массив с рядом информации о пользователе и создаст новый объект User, передавая массив конструктору.
Где можно это использовать? К примеру, если Вы создадите страницу, которая должна отображать специфические профили пользователей, Вам необходимо будет динамически брать эту информацию. Вот так Вы можете это сделать: (допустим УРЛ http://www.website.com/profile.php?userID=3)
//note: you will have to open up a database connection first.
//see Part 1 for further information on doing so.
//You'll also have to make sure that you've included the class files.
$tools = new UserTools();
$user = $tools->get($_REQUEST['userID']);
echo "Username: ".$user->username."";
echo "Joined On: ".$user->joinDate."";
Легко! Правда?
Последний штрих серверной части: global.inc.php
global.inc.php необходим для каждой страницы сайта. Почему? Таким образом мы разместим все обычные операции, которые нам понадобятся на странице. К примеру, мы начнем session_start(). Соединение с БД также откроется.
Код
<?php
require_once 'classes/User.class.php';
require_once 'classes/UserTools.class.php';
require_once 'classes/DB.class.php';
//connect to the database
$db = new DB();
$db->connect();
//initialize UserTools object
$userTools = new UserTools();
//start the session
session_start();
//refresh session variables if logged in
if(isset($_SESSION['logged_in'])) {
$user = unserialize($_SESSION['user']);
$_SESSION['user'] = serialize($userTools->get($user->id));
}
?>
Что он делает?
Тут происходит несколько вещей. Прежде всего, мы открываем соединение с базой.
После соединения, мы начинаем функцию session_start(). Функция создает сессию или продолжает текущую, если пользователь уже залогинен. Поскольку наше приложение рассчитано на то, чтобы пользователи входили\выходили, эта функция обязательна на каждой странице.
Далее мы проверяем залогинен ли юзер. Если да - мы обновим $_SESSION['user'], чтобы отображать самую последнюю информацию о юзере. К примеру, если пользователь меняет свой емейл, в сессии будет храниться еще старый. Но с помощью авто обновления такого не случится.
Что же дальше?
Теперь мы готовы к созданию видимой части скрипта. Держите в уме - все, что мы делаем сейчас вместе можно доделать, доработать и получить очень классную систему регистрации. Можно это сделать самому (если есть знания), или же за небольшую плату найти себе программиста на разного рода фрилансерских сайтах.
В этой серии уроков мы создаем необходимый минимум и знакомим Вас с ОПП.
Регистрация пользователей (register.php)
В этой странице у нас две части. Блок кода PHP в верхней части, и HTML - в нижней.
PHP код загружает global.inc.php. Он также содержит информацию о проверке формы. Код HTML помогает нам создать простую форму. Более детальные объяснения в коде:
<?php
//register.php
require_once 'includes/global.inc.php';
//инициализируем php переменные, которые используются в форме
$username = "";
$password = "";
$password_confirm = "";
$email = "";
$error = "";
//проверить отправлена ли форма
if(isset($_POST['submit-form'])) {
//получить переменные $_POST
$username = $_POST['username'];
$password = $_POST['password'];
$password_confirm = $_POST['password-confirm'];
$email = $_POST['email'];
//инициализировать переменные для проверки формы
$success = true;
$userTools = new UserTools();
//проверить правильность заполнения формы
//проверить не занят ли этот логин
if($userTools->checkUsernameExists($username))
{
$error .= "That username is already taken.<br/> \n\r";
$success = false;
}
//проверить совпадение паролей
if($password != $password_confirm) {
$error .= "Passwords do not match.<br/> \n\r";
$success = false;
}
if($success)
{
//подготовить информацию для сохранения объекта нового пользователя
$data['username'] = $username;
$data['password'] = md5($password); //зашифровать пароль для хранения
$data['email'] = $email;
//создать новый объект пользователя
$newUser = new User($data);
//сохранить нового пользователя в БД
$newUser->save(true);
//войти
$userTools->login($username, $password);
//редирект на страницу приветствия
header("Location: welcome.php");
}
}
//Если форма не отправлена или не прошла проверку, тогда показать форму снова
?>
<html>
<head>
<title>Registration</title>
</head>
<body>
<?php echo ($error != "") ? $error : ""; ?>
<form action="register.php" method="post">
Username: <input type="text" value="<?php echo $username; ?>" name="username" /><br/>
Password: <input type="password" value="<?php echo $password; ?>" name="password" /><br/>
Password (confirm): <input type="password" value="<?php echo $password_confirm; ?>" name="password-confirm" /><br/>
E-Mail: <input type="text" value="<?php echo $email; ?>" name="email" /><br/>
<input type="submit" value="Register" name="submit-form" />
</form>
</body>
</html>
Что этот код делает?
Часть с HTML кодом должна быть предельно проста. Когда форма отправляется пользователем загружается register.php и все значения отправляются в переменную $_POST.
Значение каждого поля ввода превращается в переменную ($username, $password...). Если форма еще не отправлена, все переменные будут содержать пустую строку. Если форма отправлена, но не прошла проверку, эти переменные будут содержать значения из прошлого отправления, чтобы пользователю не пришлось заново вводить данные.
Также если форма не прошла проверку, будет показана переменная $error. В этой переменной будут содержаться причины того, почему форма не прошла проверку.
Переменная $_POST в ассоциативном массиве переменных передается текущему скрипту с помощью метода HTTP POST. Значение каждого поля доступно в $_POST используя названия поля в качестве ключа. Прежде всего мы проверяем была ли нажата кнопка "Отправить". Мы проверяем $_POST['submit-form'] с помощью функции isset(), поскольку “submit-form” было название кнопки формы.
Как только мы знаем, что кнопка нажата, можем начинать проверку формы. Мы будем использовать класс UserTools для проверки доступности имени пользователя. Если такое имя уже существует, мы тогда ставим $success на false и выводим эту ошибку в $error. Следующий шаг - это проверить совпадают ли пароли, с помощью простого сравнения.
Если форма прошла проверку, мы размещаем информацию в массив $data и используем его для создания нового объекта User. Мы вызовем функцию save() того объекта User с параметрами $isNewUser - true. Далее мы войдем в систему с помощью функции login() и сделаем редирект на welcome.php.
Вот пример страницы welcome.php
<?php
//welcome.php
require_once 'includes/global.inc.php';
//проверить вошел ли пользователь
if(!isset($_SESSION['logged_in'])) {
header("Location: login.php");
}
//взять объект user из сессии
$user = unserialize($_SESSION['user']);
?>
<html>
<head>
<title>Welcome <?php echo $user->username; ?></title>
</head>
<body>
Hey there, <?php echo $user->username; ?>. You've been registered and logged in.
Welcome! <a href="logout.php">Log Out</a> | <a href="index.php">Return to
Homepage</a>
</body>
</html>
Страницы входа и выхода
login.php
Также как и в register.php, login.php будет иметь проверку формы вверху и форму для входа снизу. В форме всего два поля. Все очень легко и просто.
<?php
//login.php
require_once 'includes/global.inc.php';
$error = "";
$username = "";
$password = "";
//проверить отправлена ли форма логина
if(isset($_POST['submit-login'])) {
$username = $_POST['username'];
$password = $_POST['password'];
$userTools = new UserTools();
if($userTools->login($username, $password)){
//удачный вход, редирект на страницу
header("Location: index.php");
}else{
$error = "Incorrect username or password. Please try again.";
}
}
?>
<html>
<head>
<title>Login</title>
</head>
<body>
<?php
if($error != "")
{
echo $error."<br/>";
}
?>
<form action="login.php" method="post">
Username: <input type="text" name="username" value="<?php echo $username; ?>"
/><br/>
Password: <input type="password" name="password" value="<?php echo $password;
?>" /><br/>
<input type="submit" value="Login" name="submit-login" />
</form>
</body>
</html>
logout.php
Страница выхода еще проще. Эта страница не нуждается в HTML. Функция этой страницы выйти из приложения с помощью функции logout(). После выхода пользователь редиректится на главную.
Вот код:
<?php
//logout.php
require_once 'includes/global.inc.php';
$userTools = new UserTools();
$userTools->logout();
header("Location: index.php");
?>
Страница настроек пользователя
Зарегистрированный пользователь, который вошел в систему может захотеть изменить некоторые данные. Для простоты примера, мы разрешим пользователям менять только электронный адрес. Вы же можете попробовать расширить этот функционал.
Все очень схоже со страницами входа и выхода. На этой странице при отправке формы мы берем текущего пользователя в качестве объекта User и выставляем ему новый емейл. Далее просто вызываем функцию save() и все у нас выходит.
Код
<?php
require_once 'includes/global.inc.php';
//Проверить вошел ли пользователь
if(!isset($_SESSION['logged_in'])) {
header("Location: login.php");
}
//взять объект user из сессии
$user = unserialize($_SESSION['user']);
//инициализировать php переменные, используемые в форме
$email = $user->email;
$message = "";
//проверить отправлена ли форма
if(isset($_POST['submit-settings'])) {
//достать переменные $_POST
$email = $_POST['email'];
$user->email = $email;
$user->save();
$message = "Settings Saved<br/>";
}
//Если форма не отправлена или не прошла проверку - показать форму снова
?>
<html>
<head>
<title>Change Settings</title>
</head>
<body>
<?php echo $message; ?>
<form action="settings.php" method="post">
E-Mail: <input type="text" value="<?php echo $email; ?>" name="email" /><br/>
<input type="submit" value="Update" name="submit-settings" />
</form>
</body>
</html>
Последний файл - index.php
С этим файлом можно делать все что угодно. Тут можете разместить любые ссылки информацию. В качестве примера предлагаю Вам простую страничку с ссылками на страницы Входа, Регистрации. Также если пользователь уже вошел, ему будут видны кнопки выхода и смены параметров.
<?php
//index.php
require_once 'includes/global.inc.php';
?>
<html>
<head>
<title>Homepage</title>
</head>
<body>
<?php if(isset($_SESSION['logged_in'])) : ?>
<?php $user = unserialize($_SESSION['user']); ?>
Hello, <?php echo $user->username; ?>. You are logged in. <a href="logout.php">Logout</a>
| <a href="settings.php">Change Email</a>
<?php else : ?>
You are not logged in. <a href="login.php">Log In</a> | <a href="register.php">Register</a>
<?php endif; ?>
</body>
</html>
Что можно добавить к этому скрипту?
- Прежде всего проверка ошибок в каждом классе
- Защита от sql инъекций и других видов опасностей, связанных с формами
- Использование include() для динамического изменения шаблона (у Евгения в курсах есть информация об этом)
- Добавление чекбокса "Запомнить меня" с использование cookies, чтобы пользователь мог оставаться долгое время залогиненым
Заключение
Я надеюсь, этот цикл уроков Вам понравился. будем искать подобные и переводить! Удачи и успехов!