Личный кабинет на PHP
Под личным кабинетом понимается место, где пользователь может редактировать данные своего профиля. Давайте сделаем страницу account.php , зайдя на которую пользователь увидит форму для редактирования данных своего профиля (кроме логина и пароля, их следует обрабатывать особым образом).
Обратите внимание на то, что id пользователя мы не передаем GET параметром — мы сделаем так, чтобы каждый пользователь на странице account.php видел данные своего профиля, а не чужого.
Для этого следует при авторизации пользователя мы должны в сессию записать id этого пользователя, вот так:
Затем по заходу на страницу account.php мы будем делать SELECT запрос, который будет доставать из БД пользователя с id из сессии:
Данные пользователя мы должны показать в форме для редактирования. Пусть, к примеру, это будут имя и фамилия пользователя:
После нажатия на кнопку отправки формы мы должны будем сделать запрос на обновление юзера:
Реализуйте описанный личный кабинет.
Как сделать личный кабинет php
Так получается что мой сайт mzcoding.com 3й раз меняет домен и движок и всегда при запуске нового сайта я обновлял статью по написанию Личного кабинета на PHP! В далеком 2010 это был простой ЛК на PHP 5.0, в 2014 это был ЛК 2.0 где мы обновили версию языка и код и добавили несколько плюшек. И вот настал 3й раз в этой и следующей статье мы напишем полноценный кабинет пользователя с регистрацией, авторизацией и базовым ЛК с модерацией! Разработку мы будем вести с применением PHP7 и Bootstrap 4, а так-же с применением некоторых компонентов symfony.
Первым делом создадим каталог с наименованием lk, далее откроем терминал по адресу данной папки и введем:
composer init
У вас должен быть глобально установлен composer, что это такое и как это сделать можно прочесть тут.
Далее composer просит ввести наименование пакета! Я назову его mzcoding/lk. Далее будут еще несколько пунктов описание, автор (можете указать их на свое усмотрение). Опция Minimum Stability Укажите dev. Далее оставляйте значения по умолчанию!
В конце у вас спросят, хотите ли вы установить какие либо зависимости? Вводим no. В итоге будет создан файл composer.json со следующим содержимым:
< "name": "mzcoding/lk", "description": "Demo lk", "type": "project", "license": "mit", "authors": [ < "name": "stanislav", "email": "mzcoding@gmail.com" >], "minimum-stability": "dev", "require": <> >
Шаг 2 — Настройка зависимостей
Теперь установим необходимые нам в работе библиотеки, первым делом установим http компоненты от symfony. Введем в терминале:
composer require symfony/http-foundation
После установки данного пакета, установим еще один, это будет контейнер зависимостей DI. В качестве контейнера будем использовать библиотеку от разработчиков symfony, pimple
Для установки выполним:
composer require pimple/pimple
Давайте взглянем на наш файл composer.json. Если все верно в секции require увидим что-то подобное:
"require": < "symfony/http-foundation": "^4.2@dev", "pimple/pimple": "^3.2@dev" >
Версии библиотек могут отличаться!
Давайте сразу после секции require, добавим секцию autoload, там мы укажем где composer может искать файлы нашего приложения и автоматически их подключать, для всех этих файлов мы укажем единое пространство имен. Итоговый вариант файла composer.json должен выглядеть следующим образом:
< "name": "mzcoding/lk", "description": "Demo lk", "type": "project", "license": "mit", "authors": [ < "name": "stanislav", "email": "mzcoding@gmail.com" >], "minimum-stability": "dev", "require": < "symfony/http-foundation": "^4.2@dev", "pimple/pimple": "^3.2@dev" >, "autoload": < "psr-4": < "App\\": "app/" >, "files": [ "helpers/helpers.php" ] > >
Так-же давайте сразу создадим все необходимые каталоги.
app — Содержит все файлы/каталоги отвечающие за логику нашего приложения.
assets — Список js/css/img файлов.
config — Файлы конфигурации приложения.
helpers — Каталог с файлом helper.php который хранит вспомогательные функции.
Шаг 3 — Создаем структуру проекта
Все наши приготовления готовы! Теперь создадим точку входа нашего проекта, в корне проекта создадим файл index.php со следующим содержимым:
factory(function ($c) < $session = new \App\Classes\Session(); return $session; >); //БД $container['db'] = function () use(&$config) < $host = $config['db']['host'] ?? 'localhost'; $database = $config['db']['database'] ?? 'postgres'; $user = $config['db']['user'] ?? 'postgres'; $password = $config['db']['password'] ?? ''; $port = $config['db']['port'] ?? '5432'; $dsn = "pgsql:host=$host;port=$port;dbname=$database;user=$user;password=$password"; $dbh = new PDO($dsn); if($dbh)< $log = "Connected to the $database database successfully!"; > return $dbh; >; //Инициализируем вход (new App\Router($container))->init(); >catch(\App\Exceptions\LkException $e)< echo $e->getMessage(); >catch (PDOException $e)< echo $e->getMessage(); >catch(\Exception $e) < echo $e->getMessage(); >
Давайте разберемся, что мы делаем в данном файле.
Первым делом мы инициализируем сессию, думаю тут все понятно. Далее мы подключаем наши зависимости:
require_once __DIR__ . ‘/vendor/autoload.php';
Подтягиваем конфигурацию из файла конфига и устанавливаем глобальную константу на текущий каталог.
Далее мы открываем конструкцию try-catch тем самым отлаживая возникшие выше в коде исключительные ситуации. Обратите внимание в данном примере мы создали свой тип исключений LkException.
В блоке try мы инициализируем (получаем) все запросы (requests), а так-же инициализируем контейнер зависимостей (DI).
Далее мы добавляем в данный контейнер, конфигурацию проекта:
$container['config'] = $config;
$container['request'] = $request;
Экземпляр класса Session (данный класс опишем немного позже)
$container['session'] = $container->factory(function ($c) < $session = new \App\Classes\Session(); return $session; >);
Обратите внимание на конструкцию добавления. Мы используем метод factory нашего контейнера, что позволяет нам получать новый экземпляр объекта данного класса каждый раз при его обращении, что соответственно позволит нам постоянно перегружать сессию при каждом запросе.
Далее мы передаем в наш контейнер подключение к БД (с помощью PDO). В нашем случае мы присваиваем переменной контейнера, анонимную функцию, которая подключается к базе и возвращает объект PDO.
//БД $container['db'] = function () use(&$config) < $host = $config['db']['host'] ?? 'localhost'; $database = $config['db']['database'] ?? 'postgres'; $user = $config['db']['user'] ?? 'postgres'; $password = $config['db']['password'] ?? ''; $port = $config['db']['port'] ?? '5432'; $dsn = "pgsql:host=$host;port=$port;dbname=$database;user=$user;password=$password"; $dbh = new PDO($dsn); if($dbh)< $log = "Connected to the $database database successfully!"; > return $dbh; >;
В качестве примера мы используем БД PostgreSql.
И в завершении мы передаем текущий процесс в другой метод, а именно вызываем метод:
(new App\Router($container))->init();
Тут мы вызываем метод init класса Router предварительно передав наш инициализированный контейнер в конструктор класса.
Что-ж далее нам нужно создать класс Router, который примет управление текущим процессом.
Создадим файл Router.php в каталоге app:
container = $container; $controller = $request->get('c') ?? 'Login'; $this->controller = ucfirst($controller)."Controller"; $this->action = $request->get('a') ?? 'index'; > /** * Подключаем классы динамически */ public function init() < $className = 'App\\Controllers\\' . $this->controller; if(!class_exists($className)) < abort404(); >$objController = new $className($this->container); $objController->action>(); > >
Тут все просто, при каждом запросе мы будем передавать наименование контроллера и метода (action) которые будут вызваны. Допустим мы хотим создать новый контакт, url на форму создания нового контакта может выглядеть следующим образом:
http://localhost:8000/index.php?c=contact&a=create
Система увидев такой код, вызовет метод create() у класса ContactController.php.
Теперь давайте опилим вспомогательный класс сессий, который мы использовали выше! Этот класс просто представляет собой обертку над глобальным массивом сессий. Создадим каталог Classes и добавим туда новый файл Session.php.
return false; > public function put($key, $value) < $_SESSION[$key] = $value; >public function get($key) < if($this->has($key)) < return $_SESSION[$key]; >return null; > public function forget($key): bool < if($this->has($key)) < unset($_SESSION[$key]); return session_destroy(); >return false; > >
Шаг 4: Контроллеры и модели
Теперь добавим дамп таблицы для пользователей которые будут регистрироваться/авторизовываться!
DROP TABLE IF EXISTS `city`; CREATE TABLE `city` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `region_id` int(11) DEFAULT NULL, `name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; # Dump of table country # ------------------------------------------------------------ DROP TABLE IF EXISTS `country`; CREATE TABLE `country` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; # Dump of table region # ------------------------------------------------------------ DROP TABLE IF EXISTS `region`; CREATE TABLE `region` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `country_id` int(11) DEFAULT NULL, `name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; # Dump of table users # ------------------------------------------------------------ DROP TABLE IF EXISTS `users`; CREATE TABLE `users` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(191) DEFAULT NULL, `surname` varchar(191) DEFAULT NULL, `email` varchar(191) NOT NULL DEFAULT '', `phone` varchar(191) DEFAULT NULL, `address` varchar(191) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Теперь давайте опишем систему аутентификации! Начнем с модели Создадим в каталоге Models базовую модель Model.php:
db = $db; > /** * Вывод любых данных * @return array */ public function getData() < $query = $this->db->query("SELECT * from table>"); return $query->fetchAll(); > public function getItem(int $id) < $query = $this->db->query("SELECT * from table> WHERE return $query->fetchObject(); > /** * Добавление любых данных * @param array $params * @return bool|object */ public function insertData(array $params) < $values = []; $out = ""; $i = 0; foreach($params as $key =>$value) < $values[] = $value; $out .= ($i) ? ",?" : "?"; $i++; >$sqlPrepare = "INSERT INTO users(".implode(",", array_keys($params)).") VALUES($out)"; $statement = $this->db->prepare($sqlPrepare); $status = $statement->execute($values); if($status) < $id = $this->db->lastInsertId(); return $this->getItem($id); > return $status; > >
Думаю здесь все понятно из комментариев, мы просто описываем базовые методы для всех моделей!
Давайте начнем с пользователей и создадим модель Users:
id; $status = $user->status; $sql = "SELECT status FROM users WHERE $query = $this->db->query($sql); $r = $query->fetchObject(); if((bool)$r->status !== $status) < $user->status = (bool)$r->status; return true; > return false; > public function IsRefreshAdmin($user): bool < $id = (int)$user->id; $isAdmin = (bool)$user->isadmin; $sql = "SELECT isadmin FROM users WHERE $query = $this->db->query($sql); $r = $query->fetchObject(); if((bool)$r->isadmin !== $isAdmin) < return true; >return false; > /** * @return array */ public function getUsersForFalseStatus() < $sql = "SELECT * FROM users WHERE status = false"; $query = $this->db->query($sql); return $query->fetchAll(); > public function login($email, $password) < $sql = "SELECT * FROM users WHERE email = '".$email."' AND password = '".$password."'"; $query = $this->db->query($sql); $result = $query->fetchObject(); return $result ?? null; > public function status(int $id, bool $status) //: void - for php 7.1 or higher < $sql = "UPDATE users SET status=? WHERE $stmt = $this->db->prepare($sql); $stmt->execute([$status, $id]); > >
Когда у нас описана модель мы можем приступить к логики приложения и сделать систему регистрации/авторизации пользователей!
Первым делом как и с моделями мы создадим базовый контроллер, для этого добавим каталог Controllers и добавим в него файл BaseController.php:
di = $di; >else < abort404(); >> /** * @return array */ protected function getConfig(): array < if($this->di) < return $this->di['config']; > $config = include(ROOT_PATH . '/config/config.php'); return $config; > /** * Генерация views и параметров * @param $file_name * @param array $params * @throws LkException */ protected function view($file_name, array $params = []) < $ext = ".phtml"; $path = __DIR__ . "/../Views/"; if(!file_exists($path . $file_name.$ext)) < throw new LKException( "View file not found" ); >ob_start(); if($params) < extract($params); >$template = include($path . $file_name . $ext); echo $template; ob_get_contents(); ob_end_flush(); > >
Конструктор данного контроллера принимает все пришедшие зависимости (например данные формы и любые другие данные сервера или пользовательского ввода)
Метод getConfig() возвращает наш файл конфигурации, а метод view() отвечает за вывод файлов представления и передачу параметров в них.
Далее давайте добавим файлы контроллеров отвечающих за регистрацию и авторизацию пользователей!
Добавим еще один контроллер RegisterController.php:
di['db'])) ->getData(); $regions = (new \App\Models\Region($this->di['db'])) ->getData(); $cities = (new \App\Models\City($this->di['db'])) ->getData(); $params = [ 'countries' => $countries, 'regions' => $regions, 'cities' => $citites, 'config' => $this->getConfig() ]; return $this->view('register',$params); > /** * Обработчик регистрации */ public function register() < // dump($this->di['request']->server->get('REQUEST_METHOD')); $requestMethod = $this->di['request']->server->get('REQUEST_METHOD'); if($requestMethod != 'POST') < abort404(); >$data = $this->di['request']->request->all(); /** ТУТ МОЖЕТ БЫТЬ ВАЛИДАЦИЯ ДАННЫХ * П.С: Я ее опускаю */ if(isset($data['password_confirmation'])) < unset($data['password_confirmation']); >$data['password'] = _hpswd($data['password']); if(is_array($data)) < $db = $this->di['db']; $objUser = new User($db); if($objUser = $objUser->insertData($data)) < $this->di['session']->put('user', $objUser); return redirect($this->getConfig()['url'] .'/?c=login'); > dump('Error insert database'); > > >
Теперь посмотрим как будет выглядеть представление, по факту это просто форма регистрации register.phtml:
Регистрация на сайте /assets/bootstrap.min.css" rel="stylesheet"> /assets/signin.css" rel="stylesheet"> /assets/main.css" rel="stylesheet"> 