Этот урок набрал набрал достаточно большое количество
комментариев и дальнейшее его комментирование отключено.
Если вы хотели убедиться в правильности выполнения ДЗ или у вас возник вопрос по уроку,
посмотрите ранее добавленные комментарии, кликнув по кнопке ниже. Скорее всего вы найдете там то, что искали.
Если это не помогло - задайте вопрос в чате в телеграме - https://t.me/php_zone
У меня почему-то не получается разлогинить пользователя, я пытаюсь удалить куки с токеном и после этого вызвать getUserByToken() что бы "удалить" пользователя (null), но безуспешно. Или это по другому делается?
так при этом же остается объект пользователя на всех страницах, то есть любой контроллер да и вьюхи будут его видеть и это = что он все еще в системе (у нас же все проверки в шаблонах и контроллерах на !empty($user)). Разве не так?
После успешного логина почему то куки принимают вид:
Set-Cookie: token=3%3A; path=/; HttpOnly
Почему токен так задается не правильно? Из-за этого у меня не определяет юзера и соответственно не работает ничего.
class UsersAuthService:
/**
* @param User $user
*/
public static function createToken(User $user)
{
$token = $user->getId() . ':' . $user->refreshAuthToken();
setcookie('token', $token, 0, '/', '', false, true);
}
/**
* @return User
*/
public static function getUserByToken(): ?User
{
$token = $_COOKIE['token'] ?? '';
if (empty($token)) {
return null;
}
[$userId, $authToken] = explode(':', $token, 2);
$user = User::getById((int) $userId);
if ($user === null) {
return null;
}
if ($user->getAuthToken() !== $authToken) {
return null;
}
return $user;
}
Models/User.php:
/**
* @param array $loginData
* @return User
* @throws InvalidArgumentException
*/
public static function login(array $loginData): User
{
if (empty($loginData['email'])) {
throw new InvalidArgumentException('Enter email');
}
if (empty($loginData['password'])) {
throw new InvalidArgumentException('Enter password');
}
$user = User::findOneByColumn('email', $loginData['email']);
if ($user === null) {
throw new InvalidArgumentException('Can\'t find user with this email');
}
if (!password_verify($loginData['password'], $user->getPasswordHash())) {
throw new InvalidArgumentException('Incorrect password!');
}
/*if (!$user->isConfirmed) {
throw new InvalidArgumentException('Пользователь не подтверждён');
}*/
$user->refreshAuthToken();
$user->save();
return $user;
}
/**
* @return string
*/
public function getPasswordHash(): string
{
return $this->passwordHash;
}
public function refreshAuthToken()
{
$this->authToken = sha1(random_bytes(100)) . sha1(random_bytes(100));
}
public function getAuthToken()
{
return $this->authToken;
}
UsersController:
public function login()
{
if (!empty($_POST)) {
try {
$user = User::login($_POST);
UsersAuthService::createToken($user);
header('Location: /');
exit();
} catch (InvalidArgumentException $e) {
$this->view->renderHtml('users/login.php', ['error' => $e->getMessage()]);
return;
}
}
$this->view->renderHtml('users/login.php');
}
Ну что же вы, на 30-ом уроке такие вопросы задавать неуместно. Пора уже самостоятельно находить такие ошибки :) Используйте дебаггер и смотрите, какие куки отправляются с сервера, где и как они формируются.
$token - откуда взяться этой переменной?
На этой строке возникнет ошибка. Она выведется в браузер, а следующая не сможет выполниться, из-за того, что заголовки нельзя отправлять после отправки тела ответа. Выходит, нифига не работает.
вот убрал строку & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT в строке
error_reporting = E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT , оставил только E_ALL
и ошибка стала появляться, а это нормально что ошибка теперь стала появляться и в этом коде :
В целом вижу, что у вас есть понимание того, как все работает. Но модель не должна ничего знать о куки. Вся работа с данными запросов должны обрабатываться на уровне контроллера.
public static function deleteCookie(): void
{
setcookie('token', '', -1, '/', '', false, true);
}
UsersController.php
public function logout()
{
UsersAuthService::deleteCookie();
header('Location: /');
}
Не знаю, правильно ли я сделал, что в UsersAuthService переименовал метод createToken на saveCookie и добавил второй метод deleteCookie, вместо deleteToken. Мне почему-то показалось так понятнее, ведь мы создаем новый токен при авторизации и сохраняем его в БД. А в методе createToken мы всего лишь берем его из БД и устанавливаем куки. Допустимо ли так делать, или лучше оставить как было?
Расскажите, пожалуйста, что представляют из себя "сервисы" в целом.
Какие ещё сервисы могут быть в типовых проектах?
И как понять, в какой ситуации лучше создавать сервис?
Сервис это вспомогательный класс. Однозначного ответа на этот вопрос нет, всё зависит от конкретного проекта и команды, которая этот проект пишет. Где-то сервисы создают на каждый чих (нужен какой-то вспомогательный функционал - делаем сервис). А где-то вообще обходятся без сервисов. В большинстве случаев сервис в архитектуре MVC это слой Model.
Артём, здравствуйте!
Возник вопрос.
Можете рассказать, как лучше организовать восстановление пароля?
Если требуется проверить данные формы(что они корректны), найти аккаунт в БД и если он существует, сгенерировать новый пароль и отправить его на почту.
Вопрос в том, что и где должно быть и как взаимодействовать.
Традиционно вопросы: 1.
Почему файл сервиса активирования лежит в папке user, а файл сервиса авторизации в папке services? Может их лучше в одном месте складировать - в той же папке сервисы?
2. При обработке исключений, как лучше выводить сообщения? Если в рамках этого учебного проекта - в шаблоне, в части main, или просто отдельным сообщением без всякой разметки? Нужны ли в этом случае ссылки, например перехода на главную? Или это ситуация исключительная, поэтому и выход из нее должен осуществляться кнопкой назад в браузере или переходом через адресную строку? Или это дело вкуса/хорошего тона?
3.
Аналогично предыдущему уроку - некая путаница в названиях, сильно с толку сбивает такая схожесть в названиях. И для регистрации-активации токены и для авторизации через куки - тоже токены..
4.
Если нужно запомнить пользователя на дольше, чем время работы в браузере, для каждого пользователя нужно делать две куки - для стандартного(на время работы браузера) и для того, чтобы запомнить на дольше?
Читал про использование сессий для авторизации, в т.ч. и относительно вопросов безопасности, пока не осилил, в частности, непонятна прелесть использования сессий для авторизации в контексте того, что время сессии ~20 минут, т.е. каждые 20 минут нужно перелогиниваться, если нет куки на более длительное время?
5.
Скорее риторическая ремарка... В целом - к сожалению, несмотря на то, что я нахожу причину любой ошибки, которую сделал, не проблема разобраться в любой части проекта и в реализованых взаимосвязях между ними, все еще сложно охватить пониманием всю структуру - если бы это делать самостоятельно и с нуля - что разместить в контроллере, что во фронт-контроллере, что в сервисе, что в модели...
Перечитайте урок про удаление куки в курсе для начинающих. Туда не нужно передавать никакого значения.
Исключение не обязательно должно приводить к показу "страшной страницы". 404 исключение должно обрабатываться во фронт-котроллере. Там нужно в каждом случае выводить один и тот же шаблон. И это исключение стоит бросать в контроллере. Например, когда статья не найдена.
А если к примеру произошло исключение при добавлении комментария, из-за того, что в нем содержатся матерные слова, то это исключение стоит бросать в модели, ловить в контроллере и выводить об этом сообщение рядом с формой добавления комментария.
-
Сделайте одну куку на 5 лет
Да нормас, придет понимание. Это не через неделю, и даже не через месяц приходит. Скорее всего уже на работе придет нормальное понимание. Сейчас главное понять - как текущая реализация в принципе работает. Если оно есть - супер!
Я спрашивал об отдельной опции во время залогинивания "запомнить", т.е. два разных варианта. Кому нужно включают опцию, кому не нужно - нет. Т.е. в случае, когда есть дополнительная опция.
Теперь вспомнил (debug): имя передается через setVar()в шаблон. Переделал logout в контроллере
А зачем переделали-то? Всё же норм было. Я просто спросил, есть ли понимание, почему так происходит :)
Условие тоже переделал. Но $this->user может быть null - если перегрузить страницу с шаблоном выхода.
$this->user может быть null, но если перед чем-либо поставить !, то null уже точно никогда не будет. Будет всегда либо true, либо false, потому что ! - логическое НЕ, и результатом всегда будет булево значение.
Спасибо за разъяснение (действительно - я перемудрил).
Переделал, потому что так будет логичнее (мне кажется).
По поводу понимания происходящего: объект user остается в памяти после удаления куки с токеном (и соответственно отображается имя в шаблоне) до тех пор пока не запустится заново какой-либо из контроллеров, где user и станет null (в конструкторе).
И гуглил, и урок по массивам посмотрел. Нигде не видел такого. Функция explode возвращает массив, это понятно. А вот что слева непонятно. Буду благодарен.
Я понял, а почему вы указали такие куки:
setcookie('token', $token, 0, '/', '', false, true);
а не такие:
setcookie('token', $token);
В этом есть какой-нибудь смысл?
И почему так не удаляют:
unset($_COOKIE['token']);
Нет, вы не поняли, раз задаете этот вопрос. Дело в четвертом аргументе функции, в который мы передаём '/'.
unset просто удалит её из массива. Вам же нужно удалить её на клиенте.
Я понял, что дело в четвертом аргументе, но зачем устанавливать именно так куки:
setcookie('token', $token, 0, '/', '', false, true);
, 0, '/', '', false, true - эта часть имеет какой-нибудь смысл у нас на проекте или от балды написали ее?
В документации есть описание параметров. Как мне кажется, у вас после прочтения документации могут быть вопросы, конечно. Но вряд ли по всем параметрам. Напишите, пожалуйста, что именно непонятно.
Поймите меня правильно. На данном этапе у вас уже не должно быть вопросов вроде "ничего не понятно". Максимально самостоятельно пытайтесь выполнить ДЗ и разобраться без моей помощи. Используйте дебаггер и гугл - это лучшие друзья в работе программиста. Если есть всё же конкретный вопрос, формулируйте его точнее. Поверьте, это для вашего же блага)
...
public function logout(): void
{
if ($this->user !== null) {
UsersAuthService::logout();
}
$this->view->renderHtml('users/logout.php');
return;
}
...
UsersAuthService.php
...
public static function logout(): void
{
setcookie('token', '', 1, '/');
return;
}
...
templates/logout.php
<h1>Вы вышли.</h1>
<a href="/"><h2>Вернуться на главную</h2></a>
Долго думал как поступить с методом удаления куки, реализовал его в UsersAuthService. Ведь контроллеры отвечают за передачу данных в модель и сами не изменяют их. А в UsersAuthService мы создавали куки там, и там же их и проверяем, так почему бы там их и не удалять. Вся работа с куки будет только в одном файле)
нет экранирования слэша? Работает и так, но regex101 выделяет это как ошибки.
2.
Почему метод refreshAuthToken публичный, а не приватный?
3.
почему catch в методе login после exit, а не после User::login? После User::login ловить же уже нечего
4.
Как продолжение предыдущего вопроса: Какой вариант предпочтительнее, есть ли вообще принципиальная разница между следующими вариантами:
В юзер-контроллере:
/**
* @throws ForbiddenException
*/
public function logout()
{
UserAuthService::tokenDelete();
header('Location: /');
}
В UserAuthService:
/**
* @throws ForbiddenException
*/
public static function tokenDelete()
{
if (!isset($_COOKIE['token'])) {
throw new ForbiddenException('Unauthorized access');
}
setcookie('token', '', 0, '/', '', false, true);
}
Ловить исключение во фронт- контроллере или в юзер-контроллере? Или вообще выкидывать исключение в юзер-контроллере, а ловить во фронте?
Как это представляю себе я: Ловить исключение во фронт-контроллере видится более универсальным, а выбрасывать в юзер-контроллере или в классе сервисов, мне кажется - без разницы. Сделал - выбрасывание сразу на этапе возможного возникновения ошибки, мне так видится более логичным.
5.
Наверное тоже туда до кучи вопрос - про идентификацию исключений/код ошибки - если была сделана попытка несанкционированного доступа - например из этого же урока - удаление куки неавторизованным пользователем - это 403 или 401?
6.
Теперь нам нужно научиться передавать пользователя во View.
А что, если его туда не передавать, а прямо там и получать, если он существует? Например вот так:
Да, стоит сделать приватным, снаружи не используется, вы правы
Можно и так сделать. Но так код проще выглядит, логическая цепочка идет строка за строкой, неразрывно.
Я тоже не вижу разницы, тут всё зависит от разработчика и доводов, которые он себе напридумывает)
Думаю, 401 лучше подойдет
Если честно, я не вижу в этом ничего плохого, кроме того что UserAuthService::getByToken() будет вызываться дважды - в контроллере и во вью. Чтобы избежать повторяющихся запросов в базу можно сделать кеш внутри этого сервиса.
UserAuthService::getByToken() будет вызываться дважды - в контроллере и во вью.
Почему дважды?? Я в контроллерах вызываю этот метод косвенно - посредством создания объекта класса View единожды: вызвал контроллер(любой)-> создал объект View-> в объекте View вызвал метод получения юзера.
Мне эта мысль пришла в голову еще до того, как дочитал про абстрактный контроллер, чтобы не дублировать получение юзера в каждом контроллере, а делать это в классе View. Результат как и в уроке - в объект класса View получаем данные юзера. Только я его получаю не на уровне контроллера, а прямо в View. Я если честно, и не понял, зачем именно на уровне контроллера получать пользователя и пробрасывать его в View, если это можно сделать сразу в View.
А в абстрактный контроллер попадет только конструктор с созданием объекта View,
<?php
namespace MyProject\Controllers;
use MyProject\View\View;
/**
* Class AbstractController
* @package MyProject\Controllers
*/
class AbstractController
{
/**
* @var View
*/
protected $view;
public function __construct()
{
$this->view = new View(__DIR__ . '/../../../templates/');
}
}
Потому что в контроллере без пользователя никак. Любая проверка прав без него никак не обойдётся. Но повторюсь, можете сделать кеш и будет один запрос.
Подскажите с помощью чего реализовать? В общих чертах про кэш читал только про memcached, не знаю, из той ли это области вообще..
Я попробовал в сессию записывать пользователя. Вроде работает, но насколько правильно/оптимально/безопасно?
И есть еще нюанс относительно самих куки, вернее их идентификации. Например на одной и той же машине работают два пользователя. Как этих пользователей дифференцировать по кукам? При логине второго пользователя кука первого будет затираться кукой второго пользователя. Или ситуация-когда куки установливаются со временем жизни больше длительности сессии, как в этом случае правильно разделить, именовать специфически куки между разными пользователями? Вернее, по какому параметру пользователи себя могут идентифицировать в одном и том же браузере, если они имеют долговременные куки?
И еще вопрос относительно принципа передачи юзера во View. Почему не стоит передавать объект юзера в конструктор View или напрямую аргументом в метод renderHtml? Зачем через вызов отдельного метода-сеттера из абстрактного контроллера?
Сорян, забыл ответить и закрыл вкладку. Речь не о memcache, заведите статическую переменную и складывайте в нее значение после первого чтения из базы. При втором вызове можно проверять эту переменную на пустоту и если там не пусто, то возвращать её значение.
Была мысль на счет статической переменной, даже пробовал сделать, но что-то не срослось, попробую еще)), спасибо.
Тогда еще вот этот нюанс про куки пожалуйста гляньте:
Например на одной и той же машине работают два пользователя. Как этих пользователей дифференцировать по кукам? При логине второго пользователя кука первого будет затираться кукой второго пользователя. Или ситуация-когда куки устанавливаются со временем жизни больше длительности сессии, как в этом случае правильно разделить, именовать специфически куки между разными пользователями? Вернее, по какому параметру пользователи себя могут идентифицировать в одном и том же браузере, если они имеют долговременные куки?
Нуу сделайте куки с именем token_123, где 123 - id пользователя, и такую же для второго аккаунта. И ещё одну куку - current_user, в которой будет id текущего пользователя.
use MyProject\Models\Users\User;
use MyProject\Services\UsersAuthService;
...
public function logout()
{
UsersAuthService::deleteToken();
header('Location: /phplearn.my/www/');
exit();
}
UsersAuthService.php
public static function deleteToken(): void
{
$token = null;
setcookie('token', $token, 0, '/', '', false, true);
}
У меня есть вопрос, общий. А можно ли в представлении после ob_start сразу же подключать layouts header and footer, чтобы каждый раз в новом шаблоне не подкючать эти компоненты?Если нет - пожалуйста, обьясните.Спасибо!
public function logout()
{
/*
1 проверка куки
есть - метод выхода в сервисе
нет - отображение шаблона, что вышли
*/
$token = $_COOKIE['token'] ?? '';
if (!empty($token)) {
UsersAuthService::deleteToken();
//Переход на главную страницу, хотя д.б. наверное на стра авторизации или ту с которой запрос "выйти"
header('Location: /');
return;
}
// Если уже не авторизованный запрос пытаются разлогиниться - переход на прежнюю страницу
header('Location: '.$_SERVER["HTTP_REFERER"]);
return;
}
public function profile($params)
{
var_dump($params);
var_dump($_SERVER);
}
Вроде понятен был урок и показался легким.
Сделал вывод имени пользователя в принципе легко и сразу.
Только решил чтобы строчки не были длинными ввести доп переменные для вывода.
А уже на стадии разлогинирования возникли проблемы.
Понимал, что нужно как-то передавать ид-р пользователя. Пока снова не наткнулся на точ то он содержится в кукках токена.
Мудрил несколько дней, но так нормально не удлаось решить.
Прочитал другие варианты ответов. Понял что был рядом сосвсем в приницпе.
Решил немного повторить, но внести свою логику.
Вопросы:
1 Почему сервис хранится в папке модели пользователя, а не в папке сервисы?
2 Куда и как должна быть редирект при залогинировании, если есть кукки и если нет? На главную или на ту страницу с которой была нажата/отрпавлен запрос выйти?
Потому что это сервис, относящийся к бизнес-логике пользователей. В папке Services лежат инфраструктурные сервисы: работа с БД, отправка email. Т.е. относящиеся ко всему приложению в целом и которые могут быть использованы в любой её части.
В этой строке неправильно абсолютно всё. Имя переменной должно быть в camelCase. Строки пишутся в одинарных кавычках. Комментарий бесполезный и не относящийся к делу.
Проблема с форматированием. Делайте отступы и переносы как в уроках. Для этого в шторме можно нажать Ctrl+Alt+L
Ну и дальше по коду те же ошибки. Такое чувство что вы взяли и забыли всё что было в курсе для начинающих. В общем, пока что на троечку.
Пришлось добавить проверку при авторизации что пользователь если уже авторизован то открыть страницу статьи.
Обнаруил такую уязвимость при попытке реализовать на уроке 32 комментарии
UsersController.php
.......................
public function login()
{
if (!empty($_POST)) {
try {
$user = User::login($_POST);
UsersAuthService::createToken($user);
header('Location: /');
exit();
} catch (InvalidArgumentException $e) {
$this->view->renderHtml('users/login.php', ['error' => $e->getMessage()]);
return;
}
}
if ($this->user!==null){
header('Location: /');
exit();
}
$this->view->renderHtml('users/login.php');
}
....................
Можешь кинуть код реализации ?
Код экшена? Или вообще весь?)
У меня почему-то не получается разлогинить пользователя, я пытаюсь удалить куки с токеном и после этого вызвать getUserByToken() что бы "удалить" пользователя (null), но безуспешно. Или это по другому делается?
Просто удаляйте куки и перенаправляйте на другую страницу.
так при этом же остается объект пользователя на всех страницах, то есть любой контроллер да и вьюхи будут его видеть и это = что он все еще в системе (у нас же все проверки в шаблонах и контроллерах на !empty($user)). Разве не так?
Он у вас там берется из cookie. Не будет куки - не будет и юзера.
Header:
UsersController:
routes:
Отлично!
адрес url должен быть в нижнем регистре
ну и конечно роут '~^users/exit~' => [\MyProject\Controllers\UsersController::class, 'exit'],
Отлично!
После успешного логина почему то куки принимают вид:
Set-Cookie: token=3%3A; path=/; HttpOnly
Почему токен так задается не правильно? Из-за этого у меня не определяет юзера и соответственно не работает ничего.
class UsersAuthService:
Models/User.php:
UsersController:
Ну что же вы, на 30-ом уроке такие вопросы задавать неуместно. Пора уже самостоятельно находить такие ошибки :) Используйте дебаггер и смотрите, какие куки отправляются с сервера, где и как они формируются.
Добрый вечер! решение д/з:
header.php
UsersAuthService.php
UsersController.php
Роут
Отлично. В шаблонах удобно пользоваться конструкциями с двоеточием:
Метод
Шапка
РОут
$token - откуда взяться этой переменной?
На этой строке возникнет ошибка. Она выведется в браузер, а следующая не сможет выполниться, из-за того, что заголовки нельзя отправлять после отправки тела ответа. Выходит, нифига не работает.
странно) но ошибка не возникает и работает, ща попробую проверить откуда она берется вспомнить
да, переменной нет, там null , но ошибка почему то невозникает и ест ьпоставить просто пустую строковую "" то ничего не меняется, такое может быть?
Если отключить вывод ошибок. А так там будет undefined var
в роде ща проверил все включено ошибки
может еще где то они могли быть выключены?
вот убрал строку & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT в строке
error_reporting = E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT , оставил только E_ALL
и ошибка стала появляться, а это нормально что ошибка теперь стала появляться и в этом коде :
как отсутствие переменной $user в строке if ($user instanceof User) {
Конечно нормально, если пустой POST-запрос, то этой переменной неоткуда взяться.
Спасибо. А то я уже напугался)
В целом вижу, что у вас есть понимание того, как все работает. Но модель не должна ничего знать о куки. Вся работа с данными запросов должны обрабатываться на уровне контроллера.
Понятно, тогда здесь метод логоут в моделе лишний, да?
Да. Эту логику стоит сделать в контроллере.
routes.php
header.php
User.php
UsersController.php
Бред какой-то.
И правда, исправил.
routes.php:
index.php:
UsersAuthService.php
UsersController.php
Не знаю, правильно ли я сделал, что в UsersAuthService переименовал метод createToken на saveCookie и добавил второй метод deleteCookie, вместо deleteToken. Мне почему-то показалось так понятнее, ведь мы создаем новый токен при авторизации и сохраняем его в БД. А в методе createToken мы всего лишь берем его из БД и устанавливаем куки. Допустимо ли так делать, или лучше оставить как было?
Допустимо) всё ок
Расскажите, пожалуйста, что представляют из себя "сервисы" в целом.
Какие ещё сервисы могут быть в типовых проектах?
И как понять, в какой ситуации лучше создавать сервис?
Сервис это вспомогательный класс. Однозначного ответа на этот вопрос нет, всё зависит от конкретного проекта и команды, которая этот проект пишет. Где-то сервисы создают на каждый чих (нужен какой-то вспомогательный функционал - делаем сервис). А где-то вообще обходятся без сервисов. В большинстве случаев сервис в архитектуре MVC это слой Model.
Спасибо!
Артём, здравствуйте!
Возник вопрос.
Можете рассказать, как лучше организовать восстановление пароля?
Если требуется проверить данные формы(что они корректны), найти аккаунт в БД и если он существует, сгенерировать новый пароль и отправить его на почту.
Вопрос в том, что и где должно быть и как взаимодействовать.
А в чём проблема? Возьмите несколько сайтов/форумов, и посмотрите как реализовано восстановление.
Спасибо за урок!
UsersController.php:
templates/header.php:
Отлично
routes.php
UsersController.php
UsersAuthService.php
header.php
Традиционно вопросы:
1.
Почему файл сервиса активирования лежит в папке user, а файл сервиса авторизации в папке services? Может их лучше в одном месте складировать - в той же папке сервисы?
2. При обработке исключений, как лучше выводить сообщения? Если в рамках этого учебного проекта - в шаблоне, в части main, или просто отдельным сообщением без всякой разметки? Нужны ли в этом случае ссылки, например перехода на главную? Или это ситуация исключительная, поэтому и выход из нее должен осуществляться кнопкой назад в браузере или переходом через адресную строку? Или это дело вкуса/хорошего тона?
3.
Аналогично предыдущему уроку - некая путаница в названиях, сильно с толку сбивает такая схожесть в названиях. И для регистрации-активации токены и для авторизации через куки - тоже токены..
4.
Если нужно запомнить пользователя на дольше, чем время работы в браузере, для каждого пользователя нужно делать две куки - для стандартного(на время работы браузера) и для того, чтобы запомнить на дольше?
Читал про использование сессий для авторизации, в т.ч. и относительно вопросов безопасности, пока не осилил, в частности, непонятна прелесть использования сессий для авторизации в контексте того, что время сессии ~20 минут, т.е. каждые 20 минут нужно перелогиниваться, если нет куки на более длительное время?
5.
Скорее риторическая ремарка... В целом - к сожалению, несмотря на то, что я нахожу причину любой ошибки, которую сделал, не проблема разобраться в любой части проекта и в реализованых взаимосвязях между ними, все еще сложно охватить пониманием всю структуру - если бы это делать самостоятельно и с нуля - что разместить в контроллере, что во фронт-контроллере, что в сервисе, что в модели...
Перечитайте урок про удаление куки в курсе для начинающих. Туда не нужно передавать никакого значения.
А если к примеру произошло исключение при добавлении комментария, из-за того, что в нем содержатся матерные слова, то это исключение стоит бросать в модели, ловить в контроллере и выводить об этом сообщение рядом с формой добавления комментария.
Я спрашивал об отдельной опции во время залогинивания "запомнить", т.е. два разных варианта. Кому нужно включают опцию, кому не нужно - нет. Т.е. в случае, когда есть дополнительная опция.
Тогда передавать с формы в сервис авторизации этот параметр. В зависимости от этого параметра устанавливать куку на разное время.
header.php
UserController.php
User.php
Я не понял, почему не работает setcookie('token', '', -1)
Что в документации setcookie пишут про третий аргумент? Что за -1?
Модель пользователя не должна знать ничего о куки.
Видел, что нужно поставить минусовое время жизни куки, чтобы её удалить. Возможно что-то не так понял
Вернитесь к уроку про куки на этом сайте. Там было написано, как удалять.
Домашка
routes
UsersController
header.php
Отлично!
routes.php:
UsersController.php:
exit.php:
header.php:
А если пользователя нет? Дальше по коду будет ошибка.
Почему метод getNickname() может вернуть null?
Для чего эта проверка здесь, если выше уже запросили пользователя по токену, который хранится в куки?
Для чего это?
Для чего передаётся user => null?
В контексте входа и выхода на сайте применяются слова login и logout, а не exit.
Здесь, опять-таки, нет обработки null, которая делается в контроллере.
Исправил, как я думаю. Хотя можно обойтись наверное и без шаблона выхода.
unset ($user)
- это лишнее.'user' => null
я передаю, потому что в шаблоне выхода (уже после выхода и удаления куки) выводится "Привет имя |Выйти" без передачи этого параметра.UserControllers.php:
header.php:
logout.php:
А есть понимание, почему так происходит?
Некорректно, !$user никогда не будет null.
Теперь вспомнил (debug): имя передается через setVar()в шаблон. Переделал logout в контроллере:
Условие тоже переделал. Но $this->user может быть null - если перегрузить страницу с шаблоном выхода.
А зачем переделали-то? Всё же норм было. Я просто спросил, есть ли понимание, почему так происходит :)
$this->user может быть null, но если перед чем-либо поставить !, то null уже точно никогда не будет. Будет всегда либо true, либо false, потому что ! - логическое НЕ, и результатом всегда будет булево значение.
Спасибо за разъяснение (действительно - я перемудрил).
Переделал, потому что так будет логичнее (мне кажется).
По поводу понимания происходящего: объект user остается в памяти после удаления куки с токеном (и соответственно отображается имя в шаблоне) до тех пор пока не запустится заново какой-либо из контроллеров, где user и станет null (в конструкторе).
Верно. Но разницы как вы передадите переменную в шаблон нет. И раз уж вызываете renderHtml, то проще туда его и прокинуть.
Все понял. Еще раз спасибо.
header.php
routes.php
UsersController
Отлично
header
UsersController
routes
Строки пишутся в одинарных кавычках.
Проблема с форматированием. Делайте отступы как в уроках. Для этого в шторме можно нажать Ctrl+Alt+L
templates\header.php
src\routes.php
src\MyProject\Controllers\UserController.php
Пробел потерялся. Делайте отступы как в уроках. Для этого в шторме можно нажать Ctrl+Alt+L
В остальном всё отлично
А что значит такая запись?
И гуглил, и урок по массивам посмотрел. Нигде не видел такого. Функция explode возвращает массив, это понятно. А вот что слева непонятно. Буду благодарен.
Привет
https://wiki.php.net/rfc/short_list_syntax
header.php
routes.php
UsersController.php
Почему не разлогинивает?
Правильнее будет написать logout вместо unlogin.
Перечитайте урок по cookie, станет понятно, почему не разлогинивает)
Я понял, а почему вы указали такие куки:
setcookie('token', $token, 0, '/', '', false, true);
а не такие:
setcookie('token', $token);
В этом есть какой-нибудь смысл?
И почему так не удаляют:
unset($_COOKIE['token']);
Нет, вы не поняли, раз задаете этот вопрос. Дело в четвертом аргументе функции, в который мы передаём '/'.
unset просто удалит её из массива. Вам же нужно удалить её на клиенте.
Я понял, что дело в четвертом аргументе, но зачем устанавливать именно так куки:
setcookie('token', $token, 0, '/', '', false, true);
, 0, '/', '', false, true - эта часть имеет какой-нибудь смысл у нас на проекте или от балды написали ее?
В документации есть описание параметров. Как мне кажется, у вас после прочтения документации могут быть вопросы, конечно. Но вряд ли по всем параметрам. Напишите, пожалуйста, что именно непонятно.
Поймите меня правильно. На данном этапе у вас уже не должно быть вопросов вроде "ничего не понятно". Максимально самостоятельно пытайтесь выполнить ДЗ и разобраться без моей помощи. Используйте дебаггер и гугл - это лучшие друзья в работе программиста. Если есть всё же конкретный вопрос, формулируйте его точнее. Поверьте, это для вашего же блага)
header.php
routes.php
UsersController.php
UsersAuthService.php
templates/logout.php
Долго думал как поступить с методом удаления куки, реализовал его в UsersAuthService. Ведь контроллеры отвечают за передачу данных в модель и сами не изменяют их. А в UsersAuthService мы создавали куки там, и там же их и проверяем, так почему бы там их и не удалять. Вся работа с куки будет только в одном файле)
Код отличный, и размышляете логично)
1.
Почему в роуте
нет экранирования слэша? Работает и так, но regex101 выделяет это как ошибки.
2.
Почему метод refreshAuthToken публичный, а не приватный?
3.
почему catch в методе login после exit, а не после User::login? После User::login ловить же уже нечего
4.
Как продолжение предыдущего вопроса: Какой вариант предпочтительнее, есть ли вообще принципиальная разница между следующими вариантами:
В юзер-контроллере:
В UserAuthService:
Ловить исключение во фронт- контроллере или в юзер-контроллере? Или вообще выкидывать исключение в юзер-контроллере, а ловить во фронте?
Как это представляю себе я: Ловить исключение во фронт-контроллере видится более универсальным, а выбрасывать в юзер-контроллере или в классе сервисов, мне кажется - без разницы. Сделал - выбрасывание сразу на этапе возможного возникновения ошибки, мне так видится более логичным.
5.
Наверное тоже туда до кучи вопрос - про идентификацию исключений/код ошибки - если была сделана попытка несанкционированного доступа - например из этого же урока - удаление куки неавторизованным пользователем - это 403 или 401?
6.
А что, если его туда не передавать, а прямо там и получать, если он существует? Например вот так:
клас View:
Почему дважды?? Я в контроллерах вызываю этот метод косвенно - посредством создания объекта класса View единожды: вызвал контроллер(любой)-> создал объект View-> в объекте View вызвал метод получения юзера.
Мне эта мысль пришла в голову еще до того, как дочитал про абстрактный контроллер, чтобы не дублировать получение юзера в каждом контроллере, а делать это в классе View. Результат как и в уроке - в объект класса View получаем данные юзера. Только я его получаю не на уровне контроллера, а прямо в View. Я если честно, и не понял, зачем именно на уровне контроллера получать пользователя и пробрасывать его в View, если это можно сделать сразу в View.
А в абстрактный контроллер попадет только конструктор с созданием объекта View,
Потому что в контроллере без пользователя никак. Любая проверка прав без него никак не обойдётся. Но повторюсь, можете сделать кеш и будет один запрос.
Подскажите с помощью чего реализовать? В общих чертах про кэш читал только про memcached, не знаю, из той ли это области вообще..
Я попробовал в сессию записывать пользователя. Вроде работает, но насколько правильно/оптимально/безопасно?
И есть еще нюанс относительно самих куки, вернее их идентификации. Например на одной и той же машине работают два пользователя. Как этих пользователей дифференцировать по кукам? При логине второго пользователя кука первого будет затираться кукой второго пользователя. Или ситуация-когда куки установливаются со временем жизни больше длительности сессии, как в этом случае правильно разделить, именовать специфически куки между разными пользователями? Вернее, по какому параметру пользователи себя могут идентифицировать в одном и том же браузере, если они имеют долговременные куки?
И еще вопрос относительно принципа передачи юзера во View. Почему не стоит передавать объект юзера в конструктор View или напрямую аргументом в метод renderHtml? Зачем через вызов отдельного метода-сеттера из абстрактного контроллера?
В регулярке
Если после login не стоит маркер конца строки, то принимает некорректные запросы.
Так что с кэшем и с остальным?
Сорян, забыл ответить и закрыл вкладку. Речь не о memcache, заведите статическую переменную и складывайте в нее значение после первого чтения из базы. При втором вызове можно проверять эту переменную на пустоту и если там не пусто, то возвращать её значение.
Была мысль на счет статической переменной, даже пробовал сделать, но что-то не срослось, попробую еще)), спасибо.
Тогда еще вот этот нюанс про куки пожалуйста гляньте:
Нуу сделайте куки с именем token_123, где 123 - id пользователя, и такую же для второго аккаунта. И ещё одну куку - current_user, в которой будет id текущего пользователя.
Это первое что в голову пришло. Может поинтереснее что-то придумаете
Где находится сам метод getAuthToken()?
PHP ругается из за отсутствие этого метода.
Может где-то я пропустил, так и не смог найти. :/
В модели User
?
Это некорректный адрес
Достаточно было на такой исправить
routes
header
UsersController
Отлично!
домашка
header.php
routes.php
UsersController.php
UsersAuthService.php
/phplearn.my/www - это что за ерунда? Настройте веб-сервер нормально, www не должно быть в пути, это должна быть корневая папка веб-сервера.
Артем, у меня бесплатный мамп на маке, в нем к сожалению не могу настроить корневую папку((
поэтому, чтобы код работал - приходится такие пути писать
Беда) Мож крякнутый поставить?)
роут
UsersAuthService
контроллер
Отлично
У меня есть вопрос, общий. А можно ли в представлении после ob_start сразу же подключать layouts header and footer, чтобы каждый раз в новом шаблоне не подкючать эти компоненты?Если нет - пожалуйста, обьясните.Спасибо!
Можно и так. Но могут возникнуть ситуации, когда вам вдруг внезапно понадобится страница без футера, ну или вообще без ничего.
роутер
контроллер
модель
сервис
Можно сервис UsersAuthService дергать напрямую из контроллера, модель не должна работать с куками или вызывать код, работающий с ними.
ясно, я запутался и наоборот считал что модель должна с ними работать. буду переделывать.
Нет, модель - это про данные, ActiveRecord-модель - ещё и про работу с БД. Но никак не взаимодействие с клиентом.
routes.php
UsersController.php
header.php
Отлично
Моя попытка решить домашнее задание.
routes.php
UsersController.php
UsersAuthService.php
header.php
Вроде понятен был урок и показался легким.
Сделал вывод имени пользователя в принципе легко и сразу.
Только решил чтобы строчки не были длинными ввести доп переменные для вывода.
А уже на стадии разлогинирования возникли проблемы.
Понимал, что нужно как-то передавать ид-р пользователя. Пока снова не наткнулся на точ то он содержится в кукках токена.
Мудрил несколько дней, но так нормально не удлаось решить.
Прочитал другие варианты ответов. Понял что был рядом сосвсем в приницпе.
Решил немного повторить, но внести свою логику.
Вопросы:
1 Почему сервис хранится в папке модели пользователя, а не в папке сервисы?
2 Куда и как должна быть редирект при залогинировании, если есть кукки и если нет? На главную или на ту страницу с которой была нажата/отрпавлен запрос выйти?
Наличие токена еще не означает, что пользователь авторизован.
Это здесь для чего?
Закомментированный код нужно удалить
null пишется с маленькой буквы. Фигурная скобка должна быть на той же строке что и if.
В этой строке неправильно абсолютно всё. Имя переменной должно быть в camelCase. Строки пишутся в одинарных кавычках. Комментарий бесполезный и не относящийся к делу.
Проблема с форматированием. Делайте отступы и переносы как в уроках. Для этого в шторме можно нажать Ctrl+Alt+L
Ну и дальше по коду те же ошибки. Такое чувство что вы взяли и забыли всё что было в курсе для начинающих. В общем, пока что на троечку.
Пришлось добавить проверку при авторизации что пользователь если уже авторизован то открыть страницу статьи.
Обнаруил такую уязвимость при попытке реализовать на уроке 32 комментарии
UsersController.php
А может эта проверка должна осуществляться до того как дадим сделать повторный логин?
Да, конечно, эта проверка должна быть в начале.
Спасибо!