Чат Telegram
Группа ВКонтакте
Новый комментарий

Sparkqy 03.10.2018 в 17:44

Можешь кинуть код реализации ?

ivashkevich 04.10.2018 в 07:18

Код экшена? Или вообще весь?)

Sparkqy 04.10.2018 в 16:35

У меня почему-то не получается разлогинить пользователя, я пытаюсь удалить куки с токеном и после этого вызвать getUserByToken() что бы "удалить" пользователя (null), но безуспешно. Или это по другому делается?

ivashkevich 06.10.2018 в 14:23

Просто удаляйте куки и перенаправляйте на другую страницу.

Sparkqy 08.10.2018 в 09:35

так при этом же остается объект пользователя на всех страницах, то есть любой контроллер да и вьюхи будут его видеть и это = что он все еще в системе (у нас же все проверки в шаблонах и контроллерах на !empty($user)). Разве не так?

ivashkevich 08.10.2018 в 23:06

Он у вас там берется из cookie. Не будет куки - не будет и юзера.

Kirill.K 14.10.2018 в 17:29

Header:

<td colspan="2" style="text-align: right">
            <?php if(!empty($user)): ?>
            Привет, <?= $user->getNickname() ?>  | <a href="http://myproject.loc/users/logOut">Выйти</a>
            <?php else: ?>
            <a href="http://myproject.loc/users/login">Войти</a> | <a href="http://myproject.loc/users/register">Зарегестрироваться</a>
            <? endif; ?>
        </td>

UsersController:

public function logOut()
    {
        setcookie('token', '', -1, '/', '', false, true);
        header('Location: /');
    }

routes:

'~^users/logOut~' => [\MyProject\Controllers\UsersController::class, 'logOut'],
ivashkevich 15.10.2018 в 23:03

Отлично!

tomsonst 20.12.2018 в 19:48
header.php
<tr>
        <td colspan="2" style="text-align: right">
            <?= !empty($user) ? 'Привет, ' . $user->getNickname() . ' | <a href = " ../users/exit">Выйти</a>' : '<a href="../users/login">Войдите на сайт</a> | <a href="../users/register">Зарегистрироваться</a>' ?>
        </td>
    </tr>

UsersContoroller.php

public function exit()
{
    setcookie('token', $token, 0, '/', '', false, true);
    header('Location: /');
}

ну и конечно роут '~^users/exit~' => [\MyProject\Controllers\UsersController::class, 'exit'],

ivashkevich 20.12.2018 в 19:55

Отлично!

AxLT 14.02.2019 в 02:13

После успешного логина почему то куки принимают вид:
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');
    }
ivashkevich 17.02.2019 в 21:51

Ну что же вы, на 30-ом уроке такие вопросы задавать неуместно. Пора уже самостоятельно находить такие ошибки :) Используйте дебаггер и смотрите, какие куки отправляются с сервера, где и как они формируются.

excent63 06.04.2019 в 18:55

Добрый вечер! решение д/з:

header.php

<?php if (!empty($user)) { ?>
    Привет, <?= $user->getNickname() ?> | <a href="/users/logout">Выйти</a>
<?php } else { ?>
    <a href="/users/login">Войдите на сайт</a> | <a href="/users/register">Зарегестрируйтесь</a>
<?php } ?>

UsersAuthService.php

public static function deleteToken()
{
    setcookie('token', '', false, '/', '', false, true);
}

UsersController.php

public function logOut()
{
    UsersAuthService::deleteToken();
    header('Location: /');
}

Роут

'~^users/logout~' => [\MyProject\Controllers\UsersController::class, 'logOut']
ivashkevich 08.04.2019 в 21:28

Отлично. В шаблонах удобно пользоваться конструкциями с двоеточием:

<p>
<?php if ($x === 1): ?>
kek
<?php endif; ?>
</p>
Metey 25.07.2019 в 12:06

Метод

public function logout()
    {
        setcookie('token', $token, -1, '/', '', false, true);
        header('Location: /');
    }

Шапка

<td colspan="2" style="text-align: right"> 
            <?= !empty($user) ? 'Привет, ' . $user->getNickname() .
            ' | <a href="/users/logout"> Выйти</a>' :
             '<a href="/users/login">Войти</a> |
              <a href="/users/register">Зарегистрироваться</a>' ?>
        </td>

РОут

 '~^users/logout~' => [\MyProject\Controllers\UsersController::class, 'logout'],
ivashkevich 25.07.2019 в 18:53
setcookie('token', $token, -1, '/', '', false, true);

$token - откуда взяться этой переменной?
На этой строке возникнет ошибка. Она выведется в браузер, а следующая не сможет выполниться, из-за того, что заголовки нельзя отправлять после отправки тела ответа. Выходит, нифига не работает.

Metey 25.07.2019 в 19:23

странно) но ошибка не возникает и работает, ща попробую проверить откуда она берется вспомнить

Metey 25.07.2019 в 19:29

да, переменной нет, там null , но ошибка почему то невозникает и ест ьпоставить просто пустую строковую "" то ничего не меняется, такое может быть?

ivashkevich 26.07.2019 в 07:35

Если отключить вывод ошибок. А так там будет undefined var

Metey 26.07.2019 в 13:46

в роде ща проверил все включено ошибки

error_reporting = E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT
display_errors = On
display_startup_errors = On

может еще где то они могли быть выключены?

Metey 26.07.2019 в 14:54

вот убрал строку & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT в строке
error_reporting = E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT , оставил только E_ALL
и ошибка стала появляться, а это нормально что ошибка теперь стала появляться и в этом коде :

class UsersController extends AbstractController
{
    public function signUp()
    {
        if (!empty($_POST)) {
            try {
                $user = User::signUp($_POST);
            } catch (InvalidArgumentException $e) {
                $this->view->renderHtml('users/signUp.php', ['error' => $e->getMessage()]);
                return;
            }
        }

        if ($user instanceof User) {
            $code = UserActivationService::createActivationCode($user);

            EmailSender::send($user, 'Активация', 'userActivation.php', [
                'userId' => $user->getId(),
                'code' => $code
            ]);

            $this->view->renderHtml('users/signUpSuccessful.php');
            return;
        }

как отсутствие переменной $user в строке if ($user instanceof User) {

ivashkevich 26.07.2019 в 17:24

Конечно нормально, если пустой POST-запрос, то этой переменной неоткуда взяться.

Metey 26.07.2019 в 17:59

Спасибо. А то я уже напугался)

Iliusha99 01.08.2019 в 22:03
Router: '~^users/logout~' => [UsersController::class, 'logout']

UsersController:

public function logout()
    {
        try {
            if ($this->user !== null) {
                if (User::logout()) {
                    header('Location: /');
                    exit();
                }
            } throw new AuthException('Вы не зарегистрированы', 403);
        } catch (AuthException $e){
            $this->view->renderHtml('/errors/403.php', ['error' => $e->getMessage()], 403);
        }
    }
User Model:

public static function logout(): bool
    {
        if (isset($_COOKIE['token'])) {
            setcookie("token", "", time() - 1000, "/");
            return true;
        }
        return false;
    }
ivashkevich 02.08.2019 в 17:51

В целом вижу, что у вас есть понимание того, как все работает. Но модель не должна ничего знать о куки. Вся работа с данными запросов должны обрабатываться на уровне контроллера.

Iliusha99 02.08.2019 в 17:58

Понятно, тогда здесь метод логоут в моделе лишний, да?

ivashkevich 02.08.2019 в 18:06

Да. Эту логику стоит сделать в контроллере.

Moskva 06.08.2019 в 20:43

routes.php

'~logout~' => [\MyProject\Controllers\UsersController::class, 'logout'],

header.php

<? if(!empty($user)): ?>
                <?= 'Привет, ' . $user->getNickname()?> | <a href="/logout">Выйти</a>
            <?php else: ?>
                <a href="/users/login">Войти</a> | <a href="/users/register">Зарегистрироваться</a>
            <?php endif; ?>

User.php

public function logout()
    {
        $this->logout();
    }

UsersController.php

public function logout() // разлогинивание пользователя
    {
        setcookie('token', time()-1);
        header('Location: /');
    }
ivashkevich 07.08.2019 в 03:45
User.php

public function logout()
    {
        $this->logout();
    }

Бред какой-то.

Moskva 07.08.2019 в 18:29

И правда, исправил.

artemship 29.08.2019 в 13:23

routes.php:

'~^logout$~' => [\MyProject\Controllers\UsersController::class, 'logout'],

index.php:

<?php if (!empty($user)): ?>
    Привет, <?= $user->getNickname(); ?> |
    <a href="/logout">Выйти</a>
<?php else: ?>
    <a href="/users/login">Войти</a> |
    <a href="/users/register">Зарегистрироваться</a>
<?php endif; ?>

UsersAuthService.php

    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 мы всего лишь берем его из БД и устанавливаем куки. Допустимо ли так делать, или лучше оставить как было?

ivashkevich 30.08.2019 в 04:38

Допустимо) всё ок

zeexo 17.12.2019 в 15:25

Расскажите, пожалуйста, что представляют из себя "сервисы" в целом.
Какие ещё сервисы могут быть в типовых проектах?
И как понять, в какой ситуации лучше создавать сервис?

ivashkevich 17.12.2019 в 18:37

Сервис это вспомогательный класс. Однозначного ответа на этот вопрос нет, всё зависит от конкретного проекта и команды, которая этот проект пишет. Где-то сервисы создают на каждый чих (нужен какой-то вспомогательный функционал - делаем сервис). А где-то вообще обходятся без сервисов. В большинстве случаев сервис в архитектуре MVC это слой Model.

zeexo 18.12.2019 в 22:29

Спасибо!

zeexo 29.12.2019 в 01:18

Артём, здравствуйте!
Возник вопрос.
Можете рассказать, как лучше организовать восстановление пароля?
Если требуется проверить данные формы(что они корректны), найти аккаунт в БД и если он существует, сгенерировать новый пароль и отправить его на почту.
Вопрос в том, что и где должно быть и как взаимодействовать.

ivashkevich 03.01.2020 в 03:43

А в чём проблема? Возьмите несколько сайтов/форумов, и посмотрите как реализовано восстановление.

andreskrip 14.02.2020 в 19:33

Спасибо за урок!

UsersController.php:

public function logout(): void
    {
        setcookie('token', '', 1, '/');
        header('Location: /');
    }

templates/header.php:

<?php
            if (!empty($user)): ?>
                Привет, <?= $user->getNickname() ?> | <a href="/users/logout">Выйти</a>
            <?php else: ?>
                <a href="/users/login">Войти</a> | <a href="/users/register">Зарегистрироваться</a>
            <?php endif; ?>
ivashkevich 18.02.2020 в 15:48

Отлично

OneMoreTime 20.03.2020 в 23:39

routes.php

...
'~^users/logout$~' => [UsersController::class, 'logout']
...

UsersController.php

public function logout (): void
    {
        UsersAuthService::deleteToken();
        header('Location: /');
        exit;
    }

UsersAuthService.php

public static function deleteToken(): void
    {
        $token = $_COOKIE['token'];
        setcookie('token', $token, 1, '/', '', false, true);
    }

header.php

<td colspan="2" style="text-align: right">
            <?= !empty($user) ? 'Hello, ' . $user->getNickname().' | <a href="/users/logout">Log out</a>' : '<a href="/users/login">Log in</a> | <a href="/users/register">Sign Up</a>' ?>
        </td>

Традиционно вопросы:
1.
Почему файл сервиса активирования лежит в папке user, а файл сервиса авторизации в папке services? Может их лучше в одном месте складировать - в той же папке сервисы?

2. При обработке исключений, как лучше выводить сообщения? Если в рамках этого учебного проекта - в шаблоне, в части main, или просто отдельным сообщением без всякой разметки? Нужны ли в этом случае ссылки, например перехода на главную? Или это ситуация исключительная, поэтому и выход из нее должен осуществляться кнопкой назад в браузере или переходом через адресную строку? Или это дело вкуса/хорошего тона?

3.
Аналогично предыдущему уроку - некая путаница в названиях, сильно с толку сбивает такая схожесть в названиях. И для регистрации-активации токены и для авторизации через куки - тоже токены..

4.
Если нужно запомнить пользователя на дольше, чем время работы в браузере, для каждого пользователя нужно делать две куки - для стандартного(на время работы браузера) и для того, чтобы запомнить на дольше?
Читал про использование сессий для авторизации, в т.ч. и относительно вопросов безопасности, пока не осилил, в частности, непонятна прелесть использования сессий для авторизации в контексте того, что время сессии ~20 минут, т.е. каждые 20 минут нужно перелогиниваться, если нет куки на более длительное время?

5.
Скорее риторическая ремарка... В целом - к сожалению, несмотря на то, что я нахожу причину любой ошибки, которую сделал, не проблема разобраться в любой части проекта и в реализованых взаимосвязях между ними, все еще сложно охватить пониманием всю структуру - если бы это делать самостоятельно и с нуля - что разместить в контроллере, что во фронт-контроллере, что в сервисе, что в модели...

ivashkevich 21.03.2020 в 18:00
        $token = $_COOKIE['token'];
        setcookie('token', $token, 1, '/', '', false, true);

Перечитайте урок про удаление куки в курсе для начинающих. Туда не нужно передавать никакого значения.

  1. Исключение не обязательно должно приводить к показу "страшной страницы". 404 исключение должно обрабатываться во фронт-котроллере. Там нужно в каждом случае выводить один и тот же шаблон. И это исключение стоит бросать в контроллере. Например, когда статья не найдена.
    А если к примеру произошло исключение при добавлении комментария, из-за того, что в нем содержатся матерные слова, то это исключение стоит бросать в модели, ловить в контроллере и выводить об этом сообщение рядом с формой добавления комментария.
  2. -
  3. Сделайте одну куку на 5 лет
  4. Да нормас, придет понимание. Это не через неделю, и даже не через месяц приходит. Скорее всего уже на работе придет нормальное понимание. Сейчас главное понять - как текущая реализация в принципе работает. Если оно есть - супер!
OneMoreTime 21.03.2020 в 18:53

Сделайте одну куку на 5 лет

Я спрашивал об отдельной опции во время залогинивания "запомнить", т.е. два разных варианта. Кому нужно включают опцию, кому не нужно - нет. Т.е. в случае, когда есть дополнительная опция.

ivashkevich 21.03.2020 в 20:51

Тогда передавать с формы в сервис авторизации этот параметр. В зависимости от этого параметра устанавливать куку на разное время.

babls2332@gmail.com 23.03.2020 в 21:16

header.php

 <td colspan="2" style="text-align: right">
            <? if (!empty($user)) { ?>
                Привет,  <?=$user->getNickname()?> <a href=/users/logout>Выйти</a>
            <? } else { ?>
                <a href="/users/login">Войдите на сайт</a>
                <a href="/users/register">Регистрация</a>
            <? } ?>
        </td>

UserController.php

 public function logout() {
        User::logout();
}

User.php

 public static function logout() {
        setcookie('token', '', -1, '/', '', false, true);
        header('Location: /');
}

Я не понял, почему не работает setcookie('token', '', -1)

ivashkevich 24.03.2020 в 03:47

Что в документации setcookie пишут про третий аргумент? Что за -1?

Модель пользователя не должна знать ничего о куки.

babls2332@gmail.com 24.03.2020 в 08:19

Видел, что нужно поставить минусовое время жизни куки, чтобы её удалить. Возможно что-то не так понял

ivashkevich 24.03.2020 в 09:50

Вернитесь к уроку про куки на этом сайте. Там было написано, как удалять.

Dimitry 08.04.2020 в 04:44
//header.php
...
 <td colspan="2" style="text-align: right">
            <?= !empty($user) ? 'Привет, ' . $user->getNickname() .'|<a href="http://myproject.loc/users/logout">Выйти</a>': '<a href="http://myproject.loc/users/login">Войти</a>|<a href="http://myproject.loc/users/register">Зарегистрироваться</a>' ?>
        </td>
...

//UsersAuthService.php
...
public static function deleteToken(){
        setcookie('token', '', 0, '/', '', false, true);
    }
...

//UserController
...
public function logout(){
        if(!empty($_COOKIE)){
            UsersAuthService::deleteToken();
            header('Location: /');
            exit();
        }
    }
...

Домашка

ivashkevich 08.04.2020 в 08:26
  1. Слишком длинная строка, неудобно читать. Лучше использовать if-else конкретно в этом случае.
  2. А если незалогиненный зайдет на logout? Будет пустая страница?
Ed 17.04.2020 в 12:37

routes

'~^users/logout$~' => [\MyProject\Controllers\UsersController::class, 'logout']

UsersController

  public function logout()
  {
    setcookie('token', '', -10, '/', '', false, true);
    header('Location: /');
  }

header.php

<td colspan="2" style="text-align: right">
        <?php if(!empty($user)): ?>
          Привет <?= $user->getNickname() ?>
          <a href="/users/logout">Выйти</a>
        <?php else: ?>
          <a href="/users/login">Войти</a>
          <a href="/users/register">Зарегистрироваться</a>
        <?php endif;?>
      </td>
ivashkevich 18.04.2020 в 05:19

Отлично!

Alexann 20.04.2020 в 12:13

routes.php:

...
'~^users/exit~' => [\MyProject\Controllers\UsersController::class, 'exit'],
...

UsersController.php:

...
public function exit()
    {
        $user = UsersAuthService::getUserByToken();
        if (!empty($user)) {
            $nick = $user->getNickname() ?? '';
        };
        if (isset($_COOKIE['token'])) {
            setcookie('token', '', time() - 3600, '/', '', false, true);
        }
        unset($user);
        $this->view->renderHtml('users/exit.php', ['nick' => $nick, 'user' => null]);
    }

...

exit.php:

<?php include __DIR__ . '/../header.php'; ?>
    <div style="text-align: center;">
        <h2>До свидания <?= $nick ?>, заходите еще!</h2>
        <a href="/">Вернуться на главную страницу</a>
    </div>
<?php include __DIR__ . '/../footer.php'; ?>

header.php:

...
<tr>
        <td colspan="2" style="text-align: right">
            <?= !empty($user) ? 'Привет, ' . $user->getNickname().
                ' | <a href="/users/exit">Выйти</a>':
                '<a href="/users/login">Войти</a> | <a href="/users/register">Зарегистрироваться</a>'?>
        </td>
    </tr>
...
ivashkevich 20.04.2020 в 18:01
        if (!empty($user)) {
            $nick = $user->getNickname() ?? '';
        };

А если пользователя нет? Дальше по коду будет ошибка.

Почему метод getNickname() может вернуть null?

        if (isset($_COOKIE['token'])) {
            setcookie('token', '', time() - 3600, '/', '', false, true);
        }

Для чего эта проверка здесь, если выше уже запросили пользователя по токену, который хранится в куки?

        unset($user);

Для чего это?

        $this->view->renderHtml('users/exit.php', ['nick' => $nick, 'user' => null]);

Для чего передаётся user => null?

В контексте входа и выхода на сайте применяются слова login и logout, а не exit.

'Привет, ' . $user->getNickname().

Здесь, опять-таки, нет обработки null, которая делается в контроллере.

Alexann 21.04.2020 в 11:36

Исправил, как я думаю. Хотя можно обойтись наверное и без шаблона выхода.

unset ($user) - это лишнее.

'user' => null я передаю, потому что в шаблоне выхода (уже после выхода и удаления куки) выводится "Привет имя |Выйти" без передачи этого параметра.

UserControllers.php:

... 
public function logout()
    {
        $user = UsersAuthService::getUserByToken();
        if (!$user == null) {
            setcookie('token', '', time() - 3600, '/', '', false, true);
            $this->view->renderHtml('users/logout.php', ['nick' => $user->getNickname(),
                'user' => null]);
        } else {
            header('Location: /');
            exit();
        }

    }

header.php:

...
<td colspan="2" style="text-align: right">
            <? if(!empty($user)) { ?>
            Привет, <?= $user->getNickname() ?? '' ?> | <a href="/users/logout">Выйти</a>
            <? } else { ?>
                <a href="/users/login">Войти</a> | <a href="/users/register">Зарегистрироваться</a>
            <? } ?>
        </td>
...

logout.php:

<?php include __DIR__ . '/../header.php'; ?>
    <div style="text-align: center;">
        <h2>До свидания <?= $nick ?>, заходите еще!</h2>
        <a href="/">Вернуться на главную страницу</a>
    </div>
<?php include __DIR__ . '/../footer.php'; ?>
ivashkevich 21.04.2020 в 18:24

я передаю, потому что в шаблоне выхода (уже после выхода и удаления куки) выводится "Привет имя |Выйти" без передачи этого параметра.

А есть понимание, почему так происходит?

if (!$user == null) {

Некорректно, !$user никогда не будет null.

Alexann 21.04.2020 в 22:26

Теперь вспомнил (debug): имя передается через setVar()в шаблон. Переделал logout в контроллере:

...
public function logout()
    {
        if ($this->user) {
            setcookie('token', '', 0, '/', '', false, true);
            $this->view->setVar('user', '');
            $this->view->renderHtml('users/logout.php', ['nick' => $this->user->getNickname()]);
        } else {
            header('Location: /');
            exit();
        }

    }

Условие тоже переделал. Но $this->user может быть null - если перегрузить страницу с шаблоном выхода.

ivashkevich 22.04.2020 в 06:00

Теперь вспомнил (debug): имя передается через setVar()в шаблон. Переделал logout в контроллере

А зачем переделали-то? Всё же норм было. Я просто спросил, есть ли понимание, почему так происходит :)

Условие тоже переделал. Но $this->user может быть null - если перегрузить страницу с шаблоном выхода.

$this->user может быть null, но если перед чем-либо поставить !, то null уже точно никогда не будет. Будет всегда либо true, либо false, потому что ! - логическое НЕ, и результатом всегда будет булево значение.

Alexann 22.04.2020 в 17:18

Спасибо за разъяснение (действительно - я перемудрил).
Переделал, потому что так будет логичнее (мне кажется).
По поводу понимания происходящего: объект user остается в памяти после удаления куки с токеном (и соответственно отображается имя в шаблоне) до тех пор пока не запустится заново какой-либо из контроллеров, где user и станет null (в конструкторе).

ivashkevich 22.04.2020 в 17:22

Верно. Но разницы как вы передадите переменную в шаблон нет. И раз уж вызываете renderHtml, то проще туда его и прокинуть.

Alexann 22.04.2020 в 19:50

Все понял. Еще раз спасибо.

Dmitry.Dudin 28.04.2020 в 10:05

header.php

        <td colspan="2" style="text-align:right">
            <?= !empty($user) ? 'Привет, '.$user->getNickname().' | <a href="/users/logout">Выйти</a>' :
                '<a href="/users/login">Войти на сайт</a> | <a href="/users/register">Зарегистрироваться</a>' ?>
        </td>

routes.php


    '~^users/logout~'=>[\MyProject\Controllers\UsersController::class, 'logout'],

UsersController

    public function logOut()
    {
        setcookie('token', null, -1, '/','', false, true);
        header('Location: /users/login');
    }
ivashkevich 28.04.2020 в 19:19

Отлично

Soib Patron 05.05.2020 в 21:58

header

<td  colspan="2" style="text-align: right">
                <?php if (!empty($user)): ?>
                    Привет <b><?= $user->getNickName() ?></b> <br>
                    <a href="users/logout">Выход</a>
                <?php else: ?>
                    <a href="users/login">Вход</a>
                <?php endif ?>

            </td>

UsersController

 public function logout()
    {
        if (isset($_COOKIE['token']))
        {
            setcookie ("token", "", -1 , '/', '', false, true);
            header('Location: /');
        }else
            {
                header('Location: /');
            }
    }

routes

'~^users/logout$~' => [\controllers\UsersController::class, 'logout'],
ivashkevich 06.05.2020 в 13:56
            setcookie ("token", "", -1 , '/', '', false, true);

Строки пишутся в одинарных кавычках.

        if (isset($_COOKIE['token']))
        {
            setcookie ("token", "", -1 , '/', '', false, true);
            header('Location: /');
        }else
            {
                header('Location: /');
            }

Проблема с форматированием. Делайте отступы как в уроках. Для этого в шторме можно нажать Ctrl+Alt+L

Fill Patron 08.05.2020 в 22:24

templates\header.php

...
    <tr>
        <td colspan="2" style="text-align: right">
            <?php
            if (!empty($user)) {
                echo 'Привет, ' . $user->getNickname() . ' | <a href="/users/logout">Выйти</a>';
            }else {
                echo '<a href="/users/login">Войти</a> | <a href="users/register">Зарегистрироваться</a>';
            }
            ?>
        </td>
    </tr>
...

src\routes.php

...
    '~^users/logout$~' => [\MyProject\Controllers\UserController::class, 'logout'],
...

src\MyProject\Controllers\UserController.php

...
    public function logout()
    {
        if (!empty($this->user)) {
            setcookie('token', '', -1, '/', '', false, true);
        }
        header('Location: /');
        exit();
    }
...
ivashkevich 09.05.2020 в 09:28
}else {

Пробел потерялся. Делайте отступы как в уроках. Для этого в шторме можно нажать Ctrl+Alt+L

В остальном всё отлично

jimholder37@gmail.com Patron 13.05.2020 в 19:04

А что значит такая запись?

[$userId, $authToken] = explode(':', $token, 2);

И гуглил, и урок по массивам посмотрел. Нигде не видел такого. Функция explode возвращает массив, это понятно. А вот что слева непонятно. Буду благодарен.

ivashkevich 14.05.2020 в 12:59
dima1 20.05.2020 в 11:49

header.php

<?= !empty($user) ? 'Привет, ' . $user->getNickname() . ' | <a href="/users/unlogin">Выйти</a>': '<a href="/users/login">Войти</a> | <a href="/users/register">Зарегистрироваться</a>' ?>

routes.php

'~^users/unlogin~' => [\MyProject\Controllers\UsersController::class, 'unlogin'],

UsersController.php

    public function unlogin() {
        setcookie('token', '', -1);
        if ($_SERVER['HTTP_REFERER']) {
            header('Location: ' . $_SERVER['HTTP_REFERER']);
        }
        else {
            header('Location: /');
        }
    }

Почему не разлогинивает?

ivashkevich 20.05.2020 в 18:38

Правильнее будет написать logout вместо unlogin.

Перечитайте урок по cookie, станет понятно, почему не разлогинивает)

dima1 20.05.2020 в 22:05

Я понял, а почему вы указали такие куки:
setcookie('token', $token, 0, '/', '', false, true);
а не такие:
setcookie('token', $token);
В этом есть какой-нибудь смысл?
И почему так не удаляют:
unset($_COOKIE['token']);

ivashkevich 21.05.2020 в 07:15

Нет, вы не поняли, раз задаете этот вопрос. Дело в четвертом аргументе функции, в который мы передаём '/'.
unset просто удалит её из массива. Вам же нужно удалить её на клиенте.

dima1 21.05.2020 в 10:46

Я понял, что дело в четвертом аргументе, но зачем устанавливать именно так куки:
setcookie('token', $token, 0, '/', '', false, true);
, 0, '/', '', false, true - эта часть имеет какой-нибудь смысл у нас на проекте или от балды написали ее?

ivashkevich 22.05.2020 в 06:51

В документации есть описание параметров. Как мне кажется, у вас после прочтения документации могут быть вопросы, конечно. Но вряд ли по всем параметрам. Напишите, пожалуйста, что именно непонятно.

ivashkevich 22.05.2020 в 06:59

Поймите меня правильно. На данном этапе у вас уже не должно быть вопросов вроде "ничего не понятно". Максимально самостоятельно пытайтесь выполнить ДЗ и разобраться без моей помощи. Используйте дебаггер и гугл - это лучшие друзья в работе программиста. Если есть всё же конкретный вопрос, формулируйте его точнее. Поверьте, это для вашего же блага)

titelivus 21.05.2020 в 11:19

Если пользователь залогинен, то сделайте рядом с приветствием ссылку для выхода из этой учетной записи.

Если пользователь не залогинен, показывайте ссылки для регистрации и входа на сайт.

header.php

...
<td colspan="2" style="text-align: right">
            <?php if (!empty($user)): ?>
                Привет, <?= $user->getNickname() ?> | <a href="/users/logout">Выйти</a>
            <?php else: ?>
                <a href="/users/login">Войти</a> | <a href="/users/register">Зарегистрироваться</a>
            <?php endif; ?>
        </td>
...

Реализуйте функционал для разлогинивания (роутинг + экшен, удаляющий cookie).

routes.php

...
'~^users/logout~' => [\MyProject\Controllers\UsersController::class, 'logout'],
...

UsersController.php

...
    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 мы создавали куки там, и там же их и проверяем, так почему бы там их и не удалять. Вся работа с куки будет только в одном файле)

ivashkevich 22.05.2020 в 06:55

Код отличный, и размышляете логично)

OneMoreTime 21.05.2020 в 14:22

1.
Почему в роуте

'~^users/login~'

нет экранирования слэша? Работает и так, но 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.

А что, если его туда не передавать, а прямо там и получать, если он существует? Например вот так:

клас View:

<?php

namespace MyProject\View;

use MyProject\Models\Users\UserAuthService;

/**
 * Class View
 * @package MyProject\View
 */
class View
{
    /**
     * @var string
     */
    private $templatePath;

    /**
     * @var
     */
    private $userFromToken;

    /**
     * View constructor.
     * @param string $templatePath
     */
    public function __construct(string $templatePath)
    {
        $this->templatePath = $templatePath;
        $this->userFromToken = UserAuthService::getByToken();

    }

    /**
     * @param string $templateName
     * @param array $vars
     * @param int $code
     */
    public function renderHtml(string $templateName, array $vars = [], $code = 200)
    {
        $user = $this->userFromToken;
        http_response_code($code);
        extract($vars);
        ob_start();
        include_once $this->templatePath . '/' . $templateName;
        $buffer = ob_get_contents();
        ob_end_clean();
        echo $buffer;
    }
}
ivashkevich 22.05.2020 в 07:31
  1. Потому что ~
  2. Да, стоит сделать приватным, снаружи не используется, вы правы
  3. Можно и так сделать. Но так код проще выглядит, логическая цепочка идет строка за строкой, неразрывно.
  4. Я тоже не вижу разницы, тут всё зависит от разработчика и доводов, которые он себе напридумывает)
  5. Думаю, 401 лучше подойдет
  6. Если честно, я не вижу в этом ничего плохого, кроме того что UserAuthService::getByToken() будет вызываться дважды - в контроллере и во вью. Чтобы избежать повторяющихся запросов в базу можно сделать кеш внутри этого сервиса.
OneMoreTime 22.05.2020 в 10:29

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/');
    }

}
ivashkevich 22.05.2020 в 18:56

Потому что в контроллере без пользователя никак. Любая проверка прав без него никак не обойдётся. Но повторюсь, можете сделать кеш и будет один запрос.

OneMoreTime 23.05.2020 в 00:02

можете сделать кеш

Подскажите с помощью чего реализовать? В общих чертах про кэш читал только про memcached, не знаю, из той ли это области вообще..
Я попробовал в сессию записывать пользователя. Вроде работает, но насколько правильно/оптимально/безопасно?

И есть еще нюанс относительно самих куки, вернее их идентификации. Например на одной и той же машине работают два пользователя. Как этих пользователей дифференцировать по кукам? При логине второго пользователя кука первого будет затираться кукой второго пользователя. Или ситуация-когда куки установливаются со временем жизни больше длительности сессии, как в этом случае правильно разделить, именовать специфически куки между разными пользователями? Вернее, по какому параметру пользователи себя могут идентифицировать в одном и том же браузере, если они имеют долговременные куки?


И еще вопрос относительно принципа передачи юзера во View. Почему не стоит передавать объект юзера в конструктор View или напрямую аргументом в метод renderHtml? Зачем через вызов отдельного метода-сеттера из абстрактного контроллера?


В регулярке

'~^users/login~' => [\MyProject\Controllers\UsersController::class, 'login'],

Если после login не стоит маркер конца строки, то принимает некорректные запросы.

OneMoreTime 01.06.2020 в 09:28

Так что с кэшем и с остальным?

ivashkevich 01.06.2020 в 16:38

Сорян, забыл ответить и закрыл вкладку. Речь не о memcache, заведите статическую переменную и складывайте в нее значение после первого чтения из базы. При втором вызове можно проверять эту переменную на пустоту и если там не пусто, то возвращать её значение.

OneMoreTime 01.06.2020 в 16:54

Была мысль на счет статической переменной, даже пробовал сделать, но что-то не срослось, попробую еще)), спасибо.

Тогда еще вот этот нюанс про куки пожалуйста гляньте:

Например на одной и той же машине работают два пользователя. Как этих пользователей дифференцировать по кукам? При логине второго пользователя кука первого будет затираться кукой второго пользователя. Или ситуация-когда куки устанавливаются со временем жизни больше длительности сессии, как в этом случае правильно разделить, именовать специфически куки между разными пользователями? Вернее, по какому параметру пользователи себя могут идентифицировать в одном и том же браузере, если они имеют долговременные куки?

ivashkevich 01.06.2020 в 20:29

Нуу сделайте куки с именем token_123, где 123 - id пользователя, и такую же для второго аккаунта. И ещё одну куку - current_user, в которой будет id текущего пользователя.

ivashkevich 01.06.2020 в 20:29

Это первое что в голову пришло. Может поинтереснее что-то придумаете

studentDev 31.05.2020 в 15:33

Где находится сам метод getAuthToken()?
PHP ругается из за отсутствие этого метода.
Может где-то я пропустил, так и не смог найти. :/

ivashkevich 01.06.2020 в 07:43

В модели User

studentDev 03.06.2020 в 13:57
//Route
'~^users/logout$~' => [\MyProject\Controllers\UsersController::class, 'logout']

//Action
 public function logout()
        {
            setcookie('token', '', 0, '/');
            header('Location: /');
            return;
        }

//Header
<td colspan="2" style="text-align: right">
            <?= !empty($user) ?
                'Привет, ' . $user->getNickname() . ' | <a href="/../users/logout">Logout</a>' :
                '<a href="/../users/login">Login in</a> | <a href="/../users/register">Sign Up</a>' ?>
        </td>

?

ivashkevich 03.06.2020 в 19:02
/../users/logout

Это некорректный адрес

studentDev 04.06.2020 в 09:39
<td colspan="2" style="text-align: right">
            <?= !empty($user) ?
                'Привет, ' . $user->getNickname() . ' | <a href="/users/logout">Logout</a>' :
                '<a href="/users/login">Login in</a> | <a href="/users/register">Sign Up</a>' ?>
        </td>
ivashkevich 04.06.2020 в 11:06
/users/logout

Достаточно было на такой исправить

tsaruk4356@gmail.com Patron 07.07.2020 в 20:40

routes

'~^users/logout$~' => [\MyProject\Controllers\UsersController::class, 'logout']

header

<?= !empty($user) ? 'Привет, ' . $user->getNickname() . ' | <a href = "/test/users/logout">Выйти</a>' : '<a href = "/test/users/login">Войти</a> | <a href="/test/users/register">Зарегистрироваться </a>' ?>

UsersController

public function logout()
    {
        setcookie('token', '', 0, '/', '', false, true);
        header('Location: /test/index.php');
    }
ivashkevich 08.07.2020 в 14:25

Отлично!

IePyton Patron 04.08.2020 в 15:17

домашка

header.php

<td colspan="2" style="text-align: right">
            <?= !empty($user) ? 'Привет, ' . $user->getNickName() . ' <a href="/phplearn.my/www/users/logout">Выйти</a>' : '<a href="/phplearn.my/www/users/login">Войти</a> | <a href="/phplearn.my/www/users/register">Зарегистрироваться</a>' ?>
        </td>

routes.php

'~^users/logout~' => [\MyProject\Controllers\UsersController::class, 'logout']

UsersController.php

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);
    }
ivashkevich 05.08.2020 в 08:30

/phplearn.my/www - это что за ерунда? Настройте веб-сервер нормально, www не должно быть в пути, это должна быть корневая папка веб-сервера.

IePyton Patron 05.08.2020 в 11:36

Артем, у меня бесплатный мамп на маке, в нем к сожалению не могу настроить корневую папку((
поэтому, чтобы код работал - приходится такие пути писать

ivashkevich 05.08.2020 в 13:30

Беда) Мож крякнутый поставить?)

VitaliyB Patron 26.08.2020 в 15:06

роут

'~^users/logout~' => [\MyProject\Controllers\UsersController::class, 'logout']

UsersAuthService

public static function deleteToken()
    {
        setcookie('token', '', false, '/', '', false, true);
    }

контроллер

 public function logout()
    {

        UsersAuthService::deleteToken();
        header('Location: /');
    }
ivashkevich 27.08.2020 в 08:39

Отлично

Логические задачи с собеседований