PHP Урок 22. Профиль пользователя.
Предыдущие уроки:
Программируем на PHP - Введение
PHP - Запросы от браузера к серверу
PHP - Как работает сервер
PHP - Урок 4. PHP - интерпретатор
PHP - Урок 5. Переменные сервера и глобальные переменные
PHP - Урок 6. Конструкции print и echo. Кавычки одинарные и двойные и конкатенация строк
PHP - Урок 7. Переменные, константы и условия
PHP - Урок 8. Точка входа в приложение. Настройка mod_rewrite и файл .htaccess
PHP - Урок 9. Массивы и switch. Кодим основной каркас
PHP - Урок 10. COOKIE
PHP - Урок 11. Функции. Добавляем ядро системы core.php
PHP - Урок 12. Обзор модели MVC. Добавляем шаблоны страниц в наше приложение
PHP - Урок 13. Введение в базы данных и SQL. СУБД MySQL. Подключаемся к БД из нашего приложения
PHP - Урок 14. Регистрация пользователей на сайте
PHP Урок 15. Авторизация пользователей
PHP. Урок 16. Проверка авторизации. Функция check().
PHP Урок 17. Добавляем CSS фреймворк Bootstrap и jQuery
PHP Урок 18. Загрузка файлов на сервер
PHP Урок 19. Добавляем меню навигации
PHP Урок 21. Циклы for, while, foreach
Теория
До этого момента в нашей базе данных имеется только таблица users. Она хранит основные данные, которые необходимы для авторизации пользователей (такие как пароль, емейл и.т.п). Легко заметить, что в ней нет таких столбцов, как например имя и фамилия пользователя. В нашем приложении данные такого типа хранятся в таблице profiles.CREATE TABLE IF NOT EXISTS 'profiles' (
'user_id' bigint(20) unsigned NOT NULL,
'first_name' varchar(120) NOT NULL,
'last_name' varchar(120) NOT NULL,
'about' varchar(255) DEFAULT NULL,
PRIMARY KEY ('user_id'),
KEY 'first_name' ('first_name','last_name')
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
У архитекторов баз данных есть такое слово - нормализация. Это когда одну таблицу разбивают на две или больше. И еще есть всякие правильные (и неправильные) формы, в которых должна находится база.
Выяснение в какой форме должна быть БД происходит абсолютно без философии.
Просто перебирают все варианты и смотрят в каком состоянии быстрее работает БД (т.е время выборки, отклика и.т.п).
Где данных много стараются делать табличек по-больше, но по-короче (правильная форма больше третьей). Но опять же зависит от различных факторов. В частность движка СУБД или всего ее ПО вместе.
В нашем случае разделение данных пользователя обусловлено простой логикой: к этим двум таблицам приходит больше всего запросов на выборку. Причем по данным разных критериев.
Вот по этим критериям я и разделил на 2 таблицы данные пользователя. К тому же здесь можно подвязать такую логику - в users данные технического характера, а в profiles относящиеся к личности пользователя.
Есть и еще один критерий, о котором я уже говорил, и которому я следую при разработке своих приложений.
В компонентах любой системы всегда важна взаимосвязь.
Например: У меня есть таблица в базе данных, ее название соответствует названию шаблона, в котором отображаются данные из этой таблицы (вместо шаблона может быть компонент или чанк - кусок HTML-страницы, но у нас шаблон страницы целиком), а также у нас есть АПИ, для обращения к нему из JavaScript я также добавляю в папку /js проекта файл также соответствующий имя страницы.js.
То есть когда у таблицы БД, шаблона страницы и скрипта JS одинаковые имена - гораздо проще работать, так как видно связь (логику).
Так вот. Можно считать, что users - это общая (главная) таблица, а profiles - относится к шаблону профиля (или как говорят некоторые психолого - профайла) пользователя.
Практика
Структура таблицы БД profiles я уже выше написал. Столбец user_id имеет тип bigint - видимо у меня что-то наследственное от создателей 128-битного протокола IPv6 :)
Сам он хоть и PRIMARY KEY, но не AUTO_INCREMENT. Просто это таблица как бы продолжение users. Все новые пользователи добавляются именно в users, а в эту тоько дописываются данные уже добавленных пользователей.
Далее идут три столбца - имя, фамилия и о себе.
Здесь может заинтересовать индекс
KEY 'first_name' ('first_name','last_name')
.Сам индекс может состоять как из одного, так и нескольких полей.
Когда мы думаем ставить ли индекс на столбец или нет, мы просто думаем - будет ли поиск по нему или нет.
Понятное дело что искать человека можно как только по имени, так и по имени и фамилии.
Вот для того чтобы СУБД не мучилась мы создаем такие индексы в несколько столбцов.
В index.php в переключателе добавляем
case 'user':
if(!$this_id) header('location: /');
$title = "Данные пользователя";
$avafile = 'content/'.$this_id.'/s_ava.jpg';
//var_dump(file_exists($avafile));
if(!file_exists($avafile)) $avafile = '/content/default/s_ava.jpg';
else $avafile = '/content/'.$this_id.'/s_ava.jpg';
$user = getUserData($pdo, $this_id);
$postsCount = getUserPostsCount($pdo, $this_id);
$posts = getUserPosts($pdo, $this_id, 0, 20);
//$contents = getContentsUser($pdo, $this_id);
$mctCount = getInsUserMctsCount($pdo, $this_id);
$ins_mcts = getInsUserMcts($pdo, $this_id, 0, 20);
//var_dump($user);
//var_dump($posts);
$tpl = 'user';
break;
Здесь на самом деле ничего сложного.
Выполняется проверка есть ли у пользователя фотка на аватарку (то есть загружал ли он ее) и если есть то в переменную записывается ссылка на нее, иначе записываем ссылку на аваторку по умолчанию (с какой нибудь собакой или вопросиком).
Потом выполняются запросы к различным функциям из нашего core.php.
Таким образом мы инициализируем переменные, которые затем будем использовать в шаблоне.
В шаблоне самом следует отметить две вещи.
Во-первых у нас в переменных может быть массив строк из базы данных.
Они обрабатываются в цикле foreach о которых я писал в предыдущем уроке.
Выглядеть это будет так:
<div id="posts">
<?//var_dump($posts);?>
<div class="row">
<? $i = 0; foreach($posts as $post){ ?>
<?if($i == 4){?></div><div class="row toppost"><? $i=0; } $i++;?>
<div class="col-md-3">
<?if($post['content_id']){?>
<a href="/post/<?=$post['id']?>"><im* class="img-responsive img-thumbnail" src="/content/<?=$post['user_id']?>/s_<?=$post['content_time']?>.jpg" alt="" /></a>
<?}else{?>
<a href="/post/<?=$post['id']?>"><im* class="img-responsive img-thumbnail" src="/content/default/s_mediamo.jpg" alt="" /></a>
<?}?>
<div><a href="/post/<?=$post['id']?>"><?=annotationStr($post['text'])?></a></div>
<small><?=$post['post_time']?></small><br />
<?//var_dump($post);?>
</div>
<?}?>
</div>
</div>
* img - тег не рендерется почему то
Сначала мы смотрим как выглядет массив, потом перебираем его элементы в цикле, одновременно раскидывая на 4 колонки, чтобы смотрелось как на новостном сайте.
Второе, что нам интересно - это взаимодействие с АПИ. Рассмотрим настройки данных пользователя:
<form action="" method="post" onsubmit="return false;">
<div class="col-md-3">
<p>Имя:<br /><input class="form-control" type="text" id="first_name" name="first_name" value="<?=$user['first_name']?>" /></p>
<p>Фамилия:<br /><input class="form-control" type="text" id="last_name" name="last_name" value="<?=$user['last_name']?>" /></p>
<p>City ID:<br /><input class="form-control" type="text" id="city_id" name="city_id" value="<?=$user['city_id']?>" /></p>
</div>
<div class="col-md-9">
<p>О себе:<br /><textarea class="form-control" id="about" name="about" rows="5" cols="80"><?=$user['about']?></textarea></p>
<p><input class="btn btn-default" type="submit" id="user_update" value="Сохранить" /></p>
</div>
</form>
Здесь у каждого элемента формы есть атрибут id, например, у кнопки - user_update.
Мы их используем для получения доступа к элементам (их свойствам и значению), а также можем повесить на элемент слушатель события.
Рассмотрим как мы работаем с этой формой из js:
$("#user_update").on("click", function(){
var first_name = $("#first_name").val();
var last_name = $("#last_name").val();
var city_id = $("#city_id").val();
var about = $("#about").val();
//alert('first_name: ' + first_name + ' last_name: ' + last_name + ' city_id: ' + city_id + ' about: ' + about);
$.post( // get неправильно форматирует запрос!
'/api/updateUser',
{
"user_id": this_id,
"user_hash": this_hash,
"first_name": first_name,
"last_name": last_name,
"city_id": city_id,
"about": about,
},
function(data){
//alert(data.response);
location.reload(true);
}
);
});
Опять же не без помощи библиотеки jQuery получаем значения элементов input и textarea и с помощью $.post передаем их сервеверу.
API о котором шла речь в уроке 20 находит функцию updateUser которая у нас в coreapi.php:
// Функции работы с данными user'abs
function updateUser($pdo){
$user_id = clearInt($_POST['user_id']);
$user_hash = clearStr($_POST['user_hash']);
// TODO: validate min 1 chars
$first_name = clearStr($_POST['first_name']);
$last_name = clearStr($_POST['last_name']);
$city_id = clearStr($_POST['city_id']);
$about = clearStr($_POST['about']);
header('Content-Type: application/json');
if(check($pdo, $user_id, $user_hash)){
//print_r($_POST);
if(setProfileData($pdo, $user_id, $first_name, $last_name, $city_id, $about)){
print '{"response":1}';
}else{
print '{"response":0, "error":"No updated user data"}';
}
}else{
print '{"response":0, "error":"No avtorized user"}';
}
}
Это АПИ я устроил так, что проверка авторизации check() в каждой функции свое (хотя следовало бы вынести в отдельный сегмент кода, так как оно почти всегда похоже).
После проверки вызывается функция core.php setProfileData():
function setProfileData($pdo, $user_id, $first_name, $last_name, $city_id, $about){
$user_id = $pdo->quote($user_id);
$first_name = $pdo->quote($first_name);
$last_name = $pdo->quote($last_name);
$city_id = $pdo->quote($city_id);
$about = $pdo->quote($about);
$sql_update = "UPDATE mc_profile SET first_name=$first_name, last_name=$last_name, city_id=$city_id, about=$about WHERE user_id=$user_id";
//print $sql_update;
if($pdo->exec($sql_update)){
return true;
}else{
return false;
}
}
Что собственно в итоге и приводит к обновлению данных профиля пользователя.