Этот урок набрал набрал достаточно большое количество комментариев и дальнейшее его комментирование отключено. Если вы хотели убедиться в правильности выполнения ДЗ или у вас возник вопрос по уроку, посмотрите ранее добавленные комментарии, кликнув по кнопке ниже. Скорее всего вы найдете там то, что искали. Если это не помогло - задайте вопрос в чате в телеграме - https://t.me/php_zone
Kirill.K 15.10.2018 в 21:16

ArticlesController:

...
if ($this->user->getRole() !== admin) {
            throw new Forbidden();
        }
...

Templates\errors\403:

<?php include __DIR__ . '/../header.php'; ?>
    <h1>Для добавления статьи нужно обладать правами администратора</h1>
<?php include __DIR__ . '/../footer.php'; ?>

Index:

catch (\MyProject\Exceptions\Forbidden $e) {
    $view = new \MyProject\View\View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage()], 403);
}
ivashkevich 15.10.2018 в 23:10

Хорошо, но в Templates\errors\403 стоит выводить переменную error в шаблоне, а не хардкодить текст ошибки.

А для проверки того, является ли юзер админом можно создать в модели User метод isAdmin, который будет это проверять:

if(!$user->isAdmin()) {
...
Kirill.K 16.10.2018 в 22:35

Так?:
User:

 public function isAdmin(): bool
    {
        return $this->role === admin;
    }

ArticlesController:

if(!$this->user->isAdmin()) {
            throw new Forbidden('Для добавления статьи нужно обладать правами администратора');
        }

Templates\errors\403:

<?php include __DIR__ . '/../header.php'; ?>
<?= $error ?>
<?php include __DIR__ . '/../footer.php'; ?>
ivashkevich 17.10.2018 в 00:39

Perfect!

Kirill.K 17.10.2018 в 19:43

Ещё хотел спросить - при отображении ошибки наша шапка с ником юзера слетает. Куки живы, но отсутствует объект $user, от наличия которого у нас зависит вывод. Как бы это исправить, не могу додуматься?

ivashkevich 19.10.2018 в 00:21

На странице с ошибкой не нужны данные о пользователе. Ошибка - это ошибка, больше ничего быть там не должно.

Kirill.K 19.10.2018 в 19:02

Хорошо) Но тогда нужно сделать отдельный HeaderForErrors:

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title>Мой блог</title>
    <link rel="stylesheet" href="/styles.css">
</head>
<body>

<table class="layout">
    <tr>
        <td colspan="2" class="header">
            Мой блог
        </td>
    </tr>
    <tr>
        <td>

А то наш базовый предлагает войти или зарегистрироваться при выводе ошибки

ivashkevich 19.10.2018 в 21:55

Да, можно так.

andreskrip 15.02.2020 в 13:10

А нельзя просто передать во фронт-контроллере данные user и не делать новый шаблон?

 catch (\MyProject\Exceptions\ForbiddenException $e) {
    $view = new \MyProject\View\View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage(), 'user' => \MyProject\Services\UsersAuthService::getUserByToken()], 403);
}

У меня тогда всё отображается и в стандартном шаблоне

ivashkevich 18.02.2020 в 15:52

Не очень хорошо фронт-контроллеру обращаться к слою бизнес-логики. Он всё же должен решать архитектурные задачи.

tomsonst 20.12.2018 в 21:21
ArticlesController.php

if ($this->user->getRole !== 'admin') {
            throw new Forbidden();
        }

index.php

catch (\MyProject\Exceptions\Forbidden $e) {
    $view = new \MyProject\View\View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage(), 'user' => UsersAuthService::getUserByToken()], 403);
}

Создаем файл Forbidden.php и создаем шаблон 403.php

ivashkevich 22.12.2018 в 12:57

Хорошо

excent63 07.04.2019 в 11:41

Добрый день! д/з:

ArticlesController.php

public function add(): void
{
    if ($this->user === null) {
        throw new UnauthorizedException();
    }

    if ($this->user->getRole() !== 'admin') {
        throw new  ForbiddenException('У вас нет прав, за помощью обратитесь к администратору!');
    }
...

403.php

<?php include __DIR__ . '/../header.php'; ?>
    <strong><?= $error ?></strong>
<?php include __DIR__ . '/../footer.php'; ?>

index.php

...
 catch (\MyProject\Exceptions\ForbiddenException $e) {
    $view = new MyProject\View\View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage(), 'user' => \MyProject\Services\UsersAuthService::getUserByToken()], 403);
}
ivashkevich 08.04.2019 в 21:36

Отлично! У юзера можно завести отдельный метод:

public function isAdmin(): bool
{
    return $this->getRole() === 'admin';
}
excent63 08.04.2019 в 22:27

Вот дошёл уже до 31 урока, вроде задачи в домашках понятны, пока получается их решить))) Но вот не совсем доходит момент, когда нужно создать отдельный метод или какой тип этот метод должен отдать) Немного не понятно где создать тот или иной метод, видимо где то недочитал или недопонял) Надо как то оптимизировать своё обучение или начинать доводить блог до ума чтобы началось включение воображения, побольше практики или вернуться к началу и перечитать все по новой )))

ivashkevich 08.04.2019 в 22:33

Все придет с практикой, не переживай.

excent63 08.04.2019 в 22:35

Будем стараться) Главное что есть кому задать вопрос и получить на него правильный ответ! Спасибо за обучение!)

ivashkevich 09.04.2019 в 09:27

Пожалуйста)

Metey 25.07.2019 в 18:30

Вначале сделал, а затем учел некоторые рекомендации и немного переделал:
в index добавил ловлю эксепшнов:

catch (\MyProject\Exceptions\ForbiddenException $e) {
    $view = new \MyProject\View\View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage()], 403);
}

добавил метод isAdmin в User.php :

public function isAdmin(): bool
    {
        return $this->role === 'admin';
    }

и в метод ArticlesController add() :

if ($this->user === null) {
            throw new UnauthorizedException();
        }
        if (!$this->user->isAdmin()) {
            throw new ForbiddenException('Для доступа к данной странице необходимы права администратора!');
        }
........

сделал отдельно headerError.php и в нем изменил это :

<tr>
        <td colspan="2" style="text-align: right"> 
            <a href="/users/login">Войти как администратор</a> |
            <a href="/users/register">Зарегистрироваться</a>
        </td>
    </tr>
ivashkevich 25.07.2019 в 18:56

А для чего headerError.php? Не вижу, чтобы он где-то использовался.

Metey 25.07.2019 в 19:14

забыл сюда вставить шаблон 403.php

<?php include __DIR__ . '/../headerError.php'; ?>
    <h1><?= $error ?></h1>
<?php include __DIR__ . '/../footer.php'; ?>
ivashkevich 25.07.2019 в 19:17

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

Metey 25.07.2019 в 19:19

понял)) спасибо, возьму на заметку

Iliusha99 03.08.2019 в 16:42
Articles Controller:
if ($this->user->getRole() !== 'admin'){
            throw new AccessForbidden('Forbidden', 403);
        }

User Model:
public function getRole(): string
    {
        return $this->role;
    }

index.php:
...
catch (AccessForbidden $e) {
    $view = new View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage()], 403);
}
ivashkevich 03.08.2019 в 20:36

Норм. Но проверку на то, что юзер админ стоит проверять на уровне модели. Для этого стоит завести в ней метод isAdmin(): bool

Moskva 07.08.2019 в 15:53

User.php

...
    public function IsAdmin(): bool
    {
        return $this->role === 'admin';
    }
...

ArticlesController.php

public function add(): void // добавление статьи в БД
    {
        if($this->user === null){
            throw new UnauthorizedException();
        }
        if(!$this->user->IsAdmin()){
            throw new ForbiddenException();
        }
...

index.php

...
} catch (\MyProject\Exceptions\ForbiddenException $e){
    $view = new \MyProject\View\View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage(), 'user' => \MyProject\Services\UsersAuthService::getUserByToken()], 403);
}

403.php

<?php include __DIR__ . '/../header.php'; ?>
<h1>У вас нет прав администратора.</h1>
<?php include __DIR__ . '/../footer.php'; ?>
ivashkevich 07.08.2019 в 18:50
public function IsAdmin(): bool

Имена методов пишутся с маленькой буквы.

В остальном все хорошо.

Reechniy 02.09.2019 в 13:48
index:

 catch (\MyProject\Exceptions\ForbiddenException $e) {
    $view = new \MyProject\View\View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage()], 403);
}
 User:

 public function isAdmin(): bool
    {
        return $this->role === 'admin';
    }

AticleController:

        if(!$this->user->isAdmin()) {
            throw new ForbiddenException('Недостаточно прав, для добавления статьи');
        }

        $this->view->renderHtml('articles/add.php');
        return;
    }

Создал исключение ForbiddenException.php и 403.php как у всех
ivashkevich 02.09.2019 в 18:47

Отлично

artemship 05.09.2019 в 17:47

User:

    public function isAdmin(): bool
    {
        return $this->role === 'admin';
    }

ArticlesController:

    public function add(): void
    {
        if (!$this->user->isAdmin()) {
            throw new ForbiddenException('Статьи могут добавлять только администраторы');
        }
    ...

index.php:

} catch (ForbiddenException $e) {
    $view = new View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', [
        'error' => $e->getMessage(),
        'user' => UsersAuthService::getUserByToken()
    ], 403);
}

403.php:

<?php include __DIR__ . '/../header.php'; ?>
    <h1>Недостаточно прав</h1>
    <?= $error ?>
<?php include __DIR__ . '/../footer.php'; ?>
ivashkevich 05.09.2019 в 19:47

Супер

zeexo 10.12.2019 в 18:07

А как быть, если запрос через ajax и ответ нужно получить в json?

ArticlesController.php:

if(!$this->user->isAdmin()) {
    $this->view->displayJson(['error' => ['message' => 'У вас нет прав'], 403]);
    return;
}

Насколько это правильное решение?
Или лучше через исключение, но как тогда это реализовать?
И ещё вопрос, как сделать при ловле исключения UnauthorizedException редирект Header('Location: ...) ?

ivashkevich 11.12.2019 в 17:56

Это норм решение.

По поводу редиректа при исключении при работе с API - звучит во-первых странно. Во-вторых, ваш вопрос содержит ответ. При ловле исключения отправлять заголовок с помощью header'а location. Не знаю, что еще добавить.

zeexo 12.12.2019 в 21:05

Спасибо за подсказку.
Наверное стоило точнее объяснить.

С учётом того, что у нас index.php ловится(как в уроках) исключение:

catch (\App\Exceptions\UnauthorizedException $e) {
    $view = new \App\View\View();
    $view->renderHtml('errors/401.php', ['error' => $e->getMessage()], 401);
}

Пытаюсь сделать добавление новости через jquery ajax отправку формы.
Насколько правилен с точки зрения mvc такой подход и можно ли так работать с исключениями?

class ArticlesController extends AbstractController
{
    public function add()
    {
        try {
            $user = $this->user;
            if($user === null) {
                throw new UnauthorizedException('Вы не авторизованы!');
            }

            $report = Article::createFromArray($_POST, $this->user);
            $this->view->displayJson([
                'result' => ['message' => 'success']
            ]);

        } catch (UnauthorizedException $e) {
            $this->view->displayJson([
                'error' => ['message' => $e->getMessage()]
            ], 401);
        } catch (InvalidArgumentException $e) {
            $this->view->displayJson([
                'error' => ['message' => $e->getMessage()]
            ]);
        }
    }
}
ivashkevich 14.12.2019 в 15:23

Прям так не очень. Лучше сделать ещё отдельные исключения для API. Например, делаете ещё APIUnauthorizedException, и его ловите во фронт-контроллере, и там уже делаете:

            $view->displayJson([
                'error' => ['message' => $e->getMessage()]
            ], 401);

А в основном контроллере ловите доменное исключение, и преобразуете в исключение API:

        } catch (UnauthorizedException $e) {
            throw new APIUnauthorizedException($e->getMessage(), $e->getCode(), $e);
        }
zeexo 14.12.2019 в 16:24

Понял) Спасибо!
Но то, что содержимое методов контроллера ArticlesController(add, edit, view) сразу идёт в блоке try/catch, то есть полностью построены на исключениях, как в моём примере выше - это нормальная практика?

ivashkevich 14.12.2019 в 18:27

Да, обрабатывать в контроллере исключения уровня модели - это абсолютно нормально.

zeexo 25.12.2019 в 02:40
        } catch (UnauthorizedException $e) {
            throw new APIUnauthorizedException($e->getMessage(), $e->getCode(), $e);
        }

Допустим!
Но как разруливать ситуацию, если в разных контроллерах может быть три типа действий при ловле исключения UnauthorizedExpection:
1.

$view->renderHtml('errors/401.php', ['error' => $e->getMessage()], 401);

2.

$view->displayJson([
                'error' => ['message' => $e->getMessage()]
            ], 401);

3.

header('Location: /'); //то есть просто редирект

Тогда лучше убрать ловлю исключения UnauthorizedException из index.php ?

ivashkevich 26.12.2019 в 19:04

Для НЕ API можно бросать другое исключение, которое будет обрабатываться по-другому.

        } catch (UnauthorizedException $e) {
            throw new AppUnauthorizedException($e->getMessage(), $e->getCode(), $e);
        }
andreskrip 15.02.2020 в 13:13

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

ArticlesController.php

    public function create(): void
    {
        ...
        if (!$this->user->isAdmin()){
            throw new ForbiddenException();
        }
        ...
    }

index.php

}  catch (\MyProject\Exceptions\ForbiddenException $e) {
    $view = new \MyProject\View\View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage(), 'user' => \MyProject\Services\UsersAuthService::getUserByToken()], 403);
}

templates/errors/403.php

    <h1>У вас не достаточно прав</h1>
    <p>Создавать статьи могут только пользователи с правами администратора</p>
ivashkevich 18.02.2020 в 15:52

Отлично

OneMoreTime 21.03.2020 в 22:39

Можно было и функционал добавления статей в домашку отправить). Как-то легко пошел этот урок.
Но вопрос таки есть:

header('Location: /articles/' . $article->getId(), true, 302);

Для чего тут явно было указано true и код? По умолчанию же те же параметры?

ivashkevich 22.03.2020 в 06:49

Где вы увидели 302 по умолчанию?

OneMoreTime 22.03.2020 в 11:36

По умолчанию в данном случае)). В первую очередь практически проверил, не указав ни параметр replace ни код. При выполнении данной функции в коде- проверил через инспектор кода в части обмена заголовками. Получаем код 302, хотя это не было указано в параметрах функции. Это же редирект/код 302? Код 201, 3хх ранее не устанавливали.

Ну и из документации:

Другим специальным видом заголовков является "Location:". В этом случае функция не только отправляет этот заголовок браузеру, но также возвращает ему код состояния REDIRECT (302), если ранее не был установлен код 201 или 3xx.

Необязательный параметр replace определяет, надо ли заменять предыдущий аналогичный заголовок или заголовок того же типа. По умолчанию заголовок будет заменен

У нас же в коде явный редирект?

Я не совсем понял этот момент, поэтому и вопросы.

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

Описание функции читал несколько раз, примеры пользователей тоже, но пока не въехал.

ivashkevich 22.03.2020 в 17:44

Ок, не знал что при передаче в заголовке Location будет автоматически передан код 302.

false может передаваться когда нужно вернуть в ответе несколько заголовков с одним именем, но разными значениями. Для чего так делать - не знаю, на практике ни разу не сталкивался.

Dimitry 08.04.2020 в 07:13
//User
...
public function isAdmin() :bool
    {
        return $this->role == 'admin';
    }
...

//Forbidden
<?php

namespace MyProject\Exceptions;

class Forbidden extends \Exception
{

}

//ArticlesController
...
public function add(): void
    {
        if ($this->user === null) {
            throw new UnauthorizedException();
        }
        if (!$this->user->isAdmin()){
            throw new Forbidden();
        }

        if (!empty($_POST)) {
            try {
                $article = Article::createFromArray($_POST, $this->user);
            } catch (InvalidArgumentException $e) {
                $this->view->renderHtml('articles/add.php', ['error' => $e->getMessage()]);
                return;
            }

            header('Location: /articles/' . $article->getId(), true, 302);
            exit();
        }

        $this->view->renderHtml('articles/add.php');
    }
...

//index
...
catch (\MyProject\Exceptions\Forbidden $e) {
    $view = new \MyProject\View\View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage()], 403);
}

Домашка

ivashkevich 08.04.2020 в 08:27
return $this->role == 'admin';

Лучше сразу строго равенство использовать: ===

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

[email protected] 12.04.2020 в 13:51

ArticlesController.php

    public function add(): void
    {
        $userController = new User();
        $userController->isAdmin($this->user);
        if ($this->user === null) {
            throw new UnauthorizedException();
        }

        if (!empty($_POST)) {
            try {
                $article = Article::createFromArray($_POST, $this->user);
            } catch (InvalidArgumentException $e) {
                $this->view->renderHtml('articles/add.php', ['error' => $e->getMessage()]);
                return;
            }

            header('Location: /articles/' . $article->getId(), true, 302);
            exit();
        }

        $this->view->renderHtml('articles/add.php');

    }

User.php

 public function isAdmin(User $user) {
        if ($user->role !== 'admin') {
            throw new Forbidden('Недостаточно прав');
        }
    }

403.php

<?php include __DIR__ . '/../header.php'; ?>
    <?=$error?>
<?php include __DIR__ . '/../footer.php'; ?>

index.php

catch (\MyProject\Exceptions\Forbidden $e) {
    $view = new \MyProject\View\View(__DIR__ . '/src/MyProject/templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage()], 403);
}
ivashkevich 13.04.2020 в 14:39

Отлично

Ed 17.04.2020 в 14:56

ArtilesController

try {
        $article = Article::createFromArray($_POST, $this->user);
      } catch(InvalidArgumentException $e) {
        $this->view->renderHtml('/articles/add.php', ['error' => $e->getMessage()]);
        return;
      } catch(Forbidden $e) {
        $this->view->renderHtml('/articles/add.php', ['error' => $e->getMessage()]);
        header('Location: /articles/add', true, 403);
        return;
      }

Article

  if($author->getRole() !== 'admin') {
      throw new Forbidden('Нужно больше власти. Как дорастёте до гордого звания admin на сайте myproject.loc, тогда поговорим');
    }
ivashkevich 18.04.2020 в 05:24

Отлично. И лучше сделать у пользователя метод isAdmin(), чтобы статья не занималась сравнением роли со строкой. Она не должна знать о том, как правильно пишется роль пользователя.

Alexann 21.04.2020 в 18:57

ArticlesController.php:

...
public function add(): void
    {
        if ($this->user === null) {
            throw new UnauthorizedException();
        }
        if (!$this->user->isAdmin()) {
            throw new ForbiddenException('Для добавления статьи необходимы права администратора');
        }
...

User.php:

...    
public function isAdmin(): bool
    {
        return $this->role==='admin';
    }
...

index.php:

...
catch (\MyProject\Exceptions\ForbiddenException $e) {
    $view = new \MyProject\View\View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage()], 403);
}

403.php:

<?php include __DIR__ . '/../header.php'; ?>
<h2>Недостаточно прав!</h2>
<?= $error ?>
<?php include __DIR__ . '/../footer.php'; ?>
ivashkevich 21.04.2020 в 19:11

Отлично!

Dmitry.Dudin 28.04.2020 в 11:24

User.php

    public function isAdmin(): bool
    {
        return $this->role === 'admin';
    }

Article.php

        if(!$author->isAdmin())
        {
            throw new Forbidden('Ошибка 403: Для добавления статьи необходимо обладать правами администратора');
        }

index.php

catch (\MyProject\Exceptions\Forbidden $e){
    $view=new \MyProject\View\View(__DIR__.'/../templates/errors');
    $view->renderHtml('403.php',['error'=>$e->getMessage()],403);
}

403.php

<?php
include __DIR__.'/../header.php'; ?>
<?=$error ?>
<?php include __DIR__.'/../footer.php'; ?>

Сначала сделал проверку админа через getRole, после зашёл в комментарии, увидел, что выполнено изящнее, много где может пригодиться эта функция, и скопипастил всё нахрен :D

ivashkevich 28.04.2020 в 19:24

Ошибка 403: Для добавления статьи необходимо обладать правами администратора

Пользователю не надо писать "Ошибка 403"

В остальном ОК!

Soib 05.05.2020 в 22:02

ArticlesController

if ($this->user->getRole() !== 'admin')
  {
     throw new Forbidden('Для добавления статьи нужно обладать правами администратора');
  }

index.php

catch (\exception\Forbidden $e)
{
    $view = new \View\View(__DIR__ .'/templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage()], 403);
}

403.php

<?php include __DIR__ . '/../header.php'; ?>
    <?=$error?>
<?php include __DIR__ . '/../footer.php'; ?>
ivashkevich 06.05.2020 в 13:57

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

$this->user->getRole() !== 'admin'

Можно сделать у юзера метод isAdmin(): bool

Fill 09.05.2020 в 20:45

src\MyProject\Controllers\ArticlesController.php

...
    public function add(): void
    {
        $user = $this->user;

        if ($user === null) {
            throw new UnauthorizedException();
        }

        if (!$user->isAdmin()) {
            throw new ForbiddenException('Недостаточно прав, добавлять статьи можно только админам');
        }

        if (!empty($_POST)) {
            try {
                $article = Article::createFromArray($_POST, $user);
            } catch (InvalidArgumentException $e) {
                $this->view->renderHtml('articles/add.php', ['error' => $e->getMessage()]);
                return;
            }

            header('Location: /articles/' . $article->getId(), true, 302);
            exit();
        }
...

src\MyProject\Models\Users\User.php

...
    public function isAdmin(): bool
    {
        if ($this->role === 'admin') {
            return true;
        }
        return false;
    }
...

www\index.php

...
} catch (\MyProject\Exceptions\ForbiddenException $e) {
    $view = new \MyProject\View\View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage()], 403);
}

templates\errors\403.php

...
<?php include __DIR__ . '/../header.php'; ?>
    <?= $error ?>
<?php include __DIR__ . '/../footer.php'; ?>
...

При выводе ошибки в header.php нет $user и он отображается как для неавторизованного пользователя, в комментариях написано что строка со сведениями авторизации ненужна, сделал проверку:
templates\header.php

...
    <tr>
        <td colspan="2" style="text-align: right">
            <?php
            if ($vars['error'] === null) {
                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>
...
ivashkevich 10.05.2020 в 09:10
        if (!empty($_POST)) {

Лучше в самом начале тогда проверить что если пустой, то вот вам исключение.

            if ($vars['error'] === null) {

Почему в шаблоне $vars? И с чего взяли, что всегда будет ключ error? Это не так, тут будет ошибка уровня NOTICE.

Fill 10.05.2020 в 09:54

if (!empty($_POST)) {
Лучше в самом начале тогда проверить что если пустой, то вот вам исключение.

Не понял, в ArticlesController.php вроде все идет правильно:
1) Если не авторизован - исключение авторизации
2) Если не админ - исключение неАдмин
3) Если не пустой $_POST - createFromArray()
3.1) Если прилетело исключение из createFromArray() - выведем ошибку на этой же странице
4) Переходим на созданную статью.

Почему в шаблоне $vars? И с чего взяли, что всегда будет ключ error? Это не так, тут будет ошибка уровня NOTICE.

Исключение ForbiddenException вызывает в index.php $view->renderHtml:

} catch (\MyProject\Exceptions\ForbiddenException $e) {
    $view = new \MyProject\View\View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage()], 403);
}

src\MyProject\View\View.php

...
public function renderHtml(string $templateName, array $vars = [], int $code = 200)
    {
        http_response_code($code);
        extract($this->extraVars);
        extract($vars);

        ob_start();
        include $this->templatesPath . '/' . $templateName;
        $buffer = ob_get_contents();
        ob_end_clean();

        echo $buffer;
    }
}

Передает в renderHtml массив $vars со значением error => $e->getMessage(), его я и проверяю, при вызове renderHtml для других страниц (не содержащих ошибки) в $vars не будет ключа error, и в шапке выведется строка "Привет, $username | Выйти" или "Войи | Зарегистрироваться".

ivashkevich 10.05.2020 в 10:07

Аа, по первому пункту ок, вы просто метод до конца не дописали в комментариях.

По второму пункту - мы специально делаем extract($vars) чтобы не обращаться по ключам.
Если ключа error не будет, то тут

if ($vars['error'] === null)

будет ошибка. Потому что происходит обращение к несуществующему ключу. Сначала нужно убедиться, что ключ там вообще есть.

Fill 10.05.2020 в 10:20

Понятно, сделал проще, после extract($vars) ключи не нужны:

templates\header.php

    <tr>
        <td colspan="2" style="text-align: right">
            <?php
            if (!isset($error)) {
                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>
ivashkevich 10.05.2020 в 18:24

Так норм

dima1 20.05.2020 в 15:49

src/MyProject/Models/Users/User.php

    public function getRole(): string
    {
        return $this->role;
    }

src/MyProject/Controllers/ArticlesController.php

if ($this->user->getRole() !== 'admin') {
   throw new Forbidden('У Вас нету прав для добавления статей!');
}

templates/errors/403.php

<?php include __DIR__ . '/../header.php'; ?>
<h1>Ошибка доступа</h1>
<?= $error ?>
<?php include __DIR__ . '/../footer.php'; ?>

www/index.php

catch (\MyProject\Exceptions\ForbiddenException $e) {
    $view = new \MyProject\View\View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage(), 'user' => UsersAuthService::getUserByToken()], 403);
}

Всё работает, но как быть с юзером в шаблоне ошибки?

ivashkevich 20.05.2020 в 18:40

Можно сразу сделать юзеру метод isAdmin(): bool

Всё работает, но как быть с юзером в шаблоне ошибки?

У вас же эта проблема решена уже

dima1 20.05.2020 в 18:59

Выше вы говорили, что так нехорошо делать.

ivashkevich 20.05.2020 в 19:03

А что именно я говорил?

dima1 20.05.2020 в 21:46

Не очень хорошо фронт-контроллеру обращаться к слою бизнес-логики. Он всё же должен решать архитектурные задачи.

ivashkevich 21.05.2020 в 07:14

Это правда, но по-другому тут никак. Увы, правила иногда приходится нарушать. Это называется костыли.

titelivus 21.05.2020 в 13:18

ArticlesController.php

...
    public function add(): void
    {
        if ($this->user === null) {
            throw new UnauthorizedException();
        }

        if (!$this->user->isAdmin()) {
            throw new ForbiddenException('Доступ запрещен!');
        }
...

index.php Ловлю исключение

...
} catch (\Myproject\Exceptions\ForbiddenException $e) {
    $view = new \MyProject\View\View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage()], 403);
}
...

templates\errors\403.php

<h1>Ошибка!</h1>
<h2><?= $error ?></h2>

ForbiddenException.php

<?php

namespace MyProject\Exceptions;

class ForbiddenException extends \Exception
{
}

У меня такой вопрос, в каком файле писать use НеймспейсИсключения когда его выбрасываем.

К примеру:

TestClass.php - здесь бросаем исключение

<?php

namespace MyProject\Test;

use MyProject\Exceptions\TestException; // указал тут, так как выбрасываем исключение здесь.

class TestClass
{
    public function test()
    {
        throw new TestException('Лол Кек Чебурек =)');
    }
}

test.php - а здесь ловим

<?php

use MyProject\Test\TestClass;
// нужен ли тут use MyProject\Exceptions\TestException; ?

try {
    $test = new TestClass();
    $test->test();
} catch (TestException $) {
    return $e->getMessage();
}

Подключать в обоих файлах или в том в котором ловим или выбрасываем?

ivashkevich 22.05.2020 в 07:03

Отлично

Подключать стоит везде. Исключение - фронт-контроллер и конфиги. Там use только усложняет поиск нужного класса.

OneMoreTime 26.05.2020 в 11:09
public static function createFromArray(array $fields, User $author): Article

Недостаточно передавть в метод add только id юзера? Зачем весь объект передавать?

Сделайте так, чтобы добавлять статьи могли только пользователи с правами админа. Если это не так - бросайте исключение с новым типом - Forbidden.

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

ivashkevich 27.05.2020 в 17:47

Зачем весь объект передавать?

Так меньше вероятность того, что вместо реально существующего пользователя прилетит какая-то ерунда.

Ответ на ваш второй вопрос - конечно же да.

studentDev 04.06.2020 в 10:54
//User
 public function getRole(): string
        {
            return $this->role;
        }

//Проверка
if($this->user->getRole() !== 'admin') {
                throw new Forbidden('Forbidden', 403);
            }

//Обработка | index.php
} catch (\MyProject\Exceptions\Forbidden $e) {
        $view = new MyProject\View\View(__DIR__ . '/../templates/errors');
        $view->renderHtml('Forbidden.php', ['error' => $e->getMessage(), 'code' => $e->getCode()], 403);
    }

?

ivashkevich 04.06.2020 в 11:08

Норм! Можно сразу метод у юзера сделать isAdmin(): bool

studentDev 04.06.2020 в 12:48
//User
public function isAdmin(): bool
        {
            return $this->role === 'admin';
        }

//ArticlesController
if(!$this->user->isAdmin()) {
                throw new Forbidden('Forbidden', 403);
            }

?

ivashkevich 06.06.2020 в 08:03

Отлично

[email protected] 07.07.2020 в 21:48

User.php

public function getRole(): string
    {
        return $this->role;
    }

ArticlesController

if ($this->user->getRole() !== 'admin') {
            throw new Forbidden('Для создания статьи нужно быть админом :-(');
        }

index.php

catch (\MyProject\Exceptions\Forbidden $e) {
    $view = new \MyProject\View\View(__DIR__ . '/templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage(), 403]);
}

403.php

<?php include __DIR__ . '/../header.php'; ?>
<h1><?= $error ?></h1>
<?php include __DIR__ . '/../footer.php'; ?>

Мой вариант домашки, после её выполнения посмотрела комментарии и увидела, что более правильно будет делать проверку isAdmin на уровне модели.

ivashkevich 08.07.2020 в 14:25
if ($this->user->getRole() !== 'admin') {

Можно у модели юзера сразу метод сделать isAdmin(): bool

[email protected] 08.07.2020 в 14:47

Уже увидела в комментах после выполнения домашки, спасибо)

IePyton 04.08.2020 в 16:48

домашка

ArticlesController.php

public function add(): void
    {
        if ($this->user === null) {
            throw new UnauthorizedException();
        }

        if ($this->user->getUserRole() === 'user') {
            throw new Forbidden();
        }

User.php

    public function getUserRole(): string
    {
        return $this->role;
    }

index.php

catch (\MyProject\Exceptions\Forbidden $e) {
    $view = new \MyProject\View\View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', ['error'=>$e->getMessage()], 403);
}

403.php

<?php include __DIR__ . '/../header.php'; ?>
<h1>Нет прав доступа</h1>
Для доступа нужно <a href="/phplearn.my/www/users/login">Войти на сайт как админ</a>
<?php include __DIR__ . '/../footer.php'; ?>
ivashkevich 05.08.2020 в 08:34
        if ($this->user->getUserRole() === 'user') {

Лучше сделайте у модели User метод isAdmin(): bool и в условии используйте его следующим образом:

        if (!$this->user->isAdmin()) {

Контроллер не должен знать какие там могут быть строки в поле role у модели пользователя, он должен взаимодействовать с его интерфейсом (публичными методами).

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

IePyton 05.08.2020 в 11:47

тогда код будет таким

User.php

    public function getUserRole(): string
    {
        return $this->role;
    }

    public function isAdmin(): bool
    {
        $isAdmin = false;
        if ($this->getUserRole() === 'admin') {
            $isAdmin = true;
        }
        return $isAdmin;
    }

ArticlesController.php

        if (!$this->user->isAdmin()) {
            throw new ForbiddenException();
        }
ivashkevich 05.08.2020 в 13:31
        $isAdmin = false;
        if ($this->getUserRole() === 'admin') {
            $isAdmin = true;
        }
        return $isAdmin;

сложнааа)

        return $this->getUserRole() === 'admin';
VitaliyB 27.08.2020 в 12:36

А почему так не пойдёт, а именно через метод isAdmin?

 if ($this->getRole() !== 'admin') {
            throw new Forbidden('Ошибка доступа');
        }
ivashkevich 27.08.2020 в 16:41

Так пойдёт. Но через isAdmin в перспективе лучше, потому что если таких мест станет несколько, везде надо будет дублировать эту логику. В контроллерах, шаблонах, моделях надо будет писать $this->getRole() !== 'admin', то есть будет дублирование кода.
А так она уже будет реализована внутри сущности и нужно будет только вызвать готовый метод.

Larisa 21.01.2021 в 22:52

User.php

public function isAdmin(): bool
        {
            return $this->role === admin;
        }

ArticlesController.php

if (!$this->user->isAdmin()) {
            throw new ForbiddenException('У вас недостаточно прав для добавления статьи');
        }

index.php

}catch (\MyProject\Exceptions\ForbiddenException $e) {
    $view=new \MyProject\View\View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php',[$e->getMessage()], 403);
}

ForbiddenException.php

namespace MyProject\Exceptions;

class ForbiddenException extends \Exception
{

}

403.php

<?php include __DIR__ . '/../header.php'; ?>
<?=$error?>
<?php include __DIR__ . '/../footer.php'; ?>
ivashkevich 23.01.2021 в 20:21

Отлично

SkSeMi 31.01.2021 в 00:35

Мой вариант решения домашнего задания!

Создал шаблон 403.php

<?php include __DIR__ . '/../header.php'; ?>
    <h1>
        <?= $error; ?>
    </h1>
<?php include __DIR__ . '/../footer.php'; ?>

ForbiddenException.php

<?php

namespace MyProject\Exceptions;

class ForbiddenException extends \Exception
{
}

в index.php

................
} catch (\MyProject\Exceptions\ForbiddenException $e) {
    $view = new \MyProject\View\View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage()], 403);
}
................

В модели пользователя необходимо получить его роль.
Для этого создал публичный(Правильно?!) метод

User.php

    public function getRole()
    {
        return $this->role;
    }

ArticlesController.php
обрабатывает проверку роли пользователя в том же методе.

    public function add(): void
    {
        if ($this->user === null) {
            throw new UnauthorizedException();
        }

        if ($this->user->getRole()!="admin")
        {
            throw new ForbiddenException('Не достаточно прав доступа у Вас.');
        }

        if (!empty($_POST)) {
            try {
                $article = Article::createFromArray($_POST, $this->user);
            } catch (InvalidArgumentException $e) {
                $this->view->renderHtml('articles/add.php', ['error' => $e->getMessage()]);
                return;
            }

            header('Location: /articles/' . $article->getId(), true, 302);
            exit();
        }

        $this->view->renderHtml('articles/add.php');
    }

Остались открытыми вопросы:
?1 Как определить где какой метод в контроллере и модели должне иметь тот или иной модификатор доступа?
?2 Для каждого исключения необходимо создавать отдельный класс/файл и его ловить во фронт-контроллере или можно и на уровне обычного контроллера-обработчика в его методе?

Спасибо за урок, ответы!

ivashkevich 01.02.2021 в 06:30
        if ($this->user->getRole()!="admin")
        {

=>

        if ($this->user->getRole()!="admin") {
$this->user->getRole()!="admin"

Кавычки одинарные. Лучше завести у пользователя метод isAdmin(), в котором выполнять это сравнение.

  1. По умолчанию private. Если требуется в наследниках - protected. Если нужен доступ извне - public.
  2. Можно ловить где угодно. Во фронт-контроллере ловятся либо ошибки БД, либо исключения, брошенные из контроллера. Фронт-контроллер не должен напрямую ловить ошибки, брошенные в слое модели. Как например мы бросали InvalidArgumentException, но обрабатывали его в контроллере и далее рисовали ошибку в шаблоне.
serega19860511 30.06.2021 в 22:50

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

ivashkevich 05.07.2021 в 08:45

Да. Модель валидирует данные при своём создании. Если они некорректны, бросаем исключение уровня модели. А если неавторизованный пользователь пытается обратиться к контроллеру, где авторизация является обязательной, это уже исключительная ситуация на уровне контроллера. Со временем поймёте где какие исключения бросать, не переживайте.

Sergey503 30.07.2021 в 23:04

index.php

} catch (\MyProject\Exceptions\Forbidden $e) {
    $view = new \MyProject\View\View(__DIR__ . '/../templates/errors');
    $view->renderHtml('403.php', ['error' => $e->getMessage(), ], 403);
}

errors/403.php

<?php include __DIR__ . '/../header.php'; ?>
    <h2>У Вас недостаточно прав!</h2>
<?= $error ?>
<?php include __DIR__ . '/../footer.php'; ?>

Controllers/ArticlesController.php

        if (!$this->user->isAdmin()) {
            throw new Forbidden('Для доступа к данной странице необходимы права администратора!');
        }

Users/User.php

    public function isAdmin(): bool
    {
        return $this->role === 'admin';
    }

Exceptions/Frobidden.php

<?php

namespace MyProject\Exceptions;

class Forbidden extends \Exception
{
}
ivashkevich 06.08.2021 в 19:58

Отлично, только лучше дописать в названии класса исключения инфу о том что это исключение: ForbiddenException

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