Этот урок набрал набрал достаточно большое количество
комментариев и дальнейшее его комментирование отключено.
Если вы хотели убедиться в правильности выполнения ДЗ или у вас возник вопрос по уроку,
посмотрите ранее добавленные комментарии, кликнув по кнопке ниже. Скорее всего вы найдете там то, что искали.
Если это не помогло - задайте вопрос в чате в телеграме - https://t.me/php_zone
...
if ($this->user->getRole() !== admin) {
throw new Forbidden();
}
...
Templates\errors\403:
<?php include __DIR__ . '/../header.php'; ?>
<h1>Для добавления статьи нужно обладать правами администратора</h1>
<?php include __DIR__ . '/../footer.php'; ?>
Ещё хотел спросить - при отображении ошибки наша шапка с ником юзера слетает. Куки живы, но отсутствует объект $user, от наличия которого у нас зависит вывод. Как бы это исправить, не могу додуматься?
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'; ?>
Вот дошёл уже до 31 урока, вроде задачи в домашках понятны, пока получается их решить))) Но вот не совсем доходит момент, когда нужно создать отдельный метод или какой тип этот метод должен отдать) Немного не понятно где создать тот или иной метод, видимо где то недочитал или недопонял) Надо как то оптимизировать своё обучение или начинать доводить блог до ума чтобы началось включение воображения, побольше практики или вернуться к началу и перечитать все по новой )))
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 и в нем изменил это :
...
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:
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 как у всех
Насколько это правильное решение?
Или лучше через исключение, но как тогда это реализовать?
И ещё вопрос, как сделать при ловле исключения UnauthorizedException редирект Header('Location: ...) ?
По поводу редиректа при исключении при работе с API - звучит во-первых странно. Во-вторых, ваш вопрос содержит ответ. При ловле исключения отправлять заголовок с помощью header'а location. Не знаю, что еще добавить.
Пытаюсь сделать добавление новости через jquery ajax отправку формы.
Насколько правилен с точки зрения mvc такой подход и можно ли так работать с исключениями?
Прям так не очень. Лучше сделать ещё отдельные исключения для API. Например, делаете ещё APIUnauthorizedException, и его ловите во фронт-контроллере, и там уже делаете:
Понял) Спасибо!
Но то, что содержимое методов контроллера ArticlesController(add, edit, view) сразу идёт в блоке try/catch, то есть полностью построены на исключениях, как в моём примере выше - это нормальная практика?
По умолчанию в данном случае)). В первую очередь практически проверил, не указав ни параметр replace ни код. При выполнении данной функции в коде- проверил через инспектор кода в части обмена заголовками. Получаем код 302, хотя это не было указано в параметрах функции. Это же редирект/код 302? Код 201, 3хх ранее не устанавливали.
Ну и из документации:
Другим специальным видом заголовков является "Location:". В этом случае функция не только отправляет этот заголовок браузеру, но также возвращает ему код состояния REDIRECT (302), если ранее не был установлен код 201 или 3xx.
Необязательный параметр replace определяет, надо ли заменять предыдущий аналогичный заголовок или заголовок того же типа. По умолчанию заголовок будет заменен
У нас же в коде явный редирект?
Я не совсем понял этот момент, поэтому и вопросы.
Не очень понятны ситуации, когда будет использован параметр false.
Описание функции читал несколько раз, примеры пользователей тоже, но пока не въехал.
Ок, не знал что при передаче в заголовке Location будет автоматически передан код 302.
false может передаваться когда нужно вернуть в ответе несколько заголовков с одним именем, но разными значениями. Для чего так делать - не знаю, на практике ни разу не сталкивался.
if($author->getRole() !== 'admin') {
throw new Forbidden('Нужно больше власти. Как дорастёте до гордого звания admin на сайте myproject.loc, тогда поговорим');
}
Отлично. И лучше сделать у пользователя метод isAdmin(), чтобы статья не занималась сравнением роли со строкой. Она не должна знать о том, как правильно пишется роль пользователя.
...
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';
}
...
<?php
include __DIR__.'/../header.php'; ?>
<?=$error ?>
<?php include __DIR__.'/../footer.php'; ?>
Сначала сделал проверку админа через getRole, после зашёл в комментарии, увидел, что выполнено изящнее, много где может пригодиться эта функция, и скопипастил всё нахрен :D
...
<?php include __DIR__ . '/../header.php'; ?>
<?= $error ?>
<?php include __DIR__ . '/../footer.php'; ?>
...
При выводе ошибки в header.php нет $user и он отображается как для неавторизованного пользователя, в комментариях написано что строка со сведениями авторизации ненужна, сделал проверку:
templates\header.php
if (!empty($_POST)) {
Лучше в самом начале тогда проверить что если пустой, то вот вам исключение.
Не понял, в ArticlesController.php вроде все идет правильно:
1) Если не авторизован - исключение авторизации
2) Если не админ - исключение неАдмин
3) Если не пустой $_POST - createFromArray()
3.1) Если прилетело исключение из createFromArray() - выведем ошибку на этой же странице
4) Переходим на созданную статью.
Почему в шаблоне $vars? И с чего взяли, что всегда будет ключ error? Это не так, тут будет ошибка уровня NOTICE.
Исключение ForbiddenException вызывает в index.php $view->renderHtml:
...
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 | Выйти" или "Войи | Зарегистрироваться".
...
public function add(): void
{
if ($this->user === null) {
throw new UnauthorizedException();
}
if (!$this->user->isAdmin()) {
throw new ForbiddenException('Доступ запрещен!');
}
...
<?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();
}
Подключать в обоих файлах или в том в котором ловим или выбрасываем?
public static function createFromArray(array $fields, User $author): Article
Недостаточно передавть в метод add только id юзера? Зачем весь объект передавать?
Сделайте так, чтобы добавлять статьи могли только пользователи с правами админа. Если это не так - бросайте исключение с новым типом - Forbidden.
В подобных случаях нужно делать И исключение возможности осуществления функционала на уровне кода - выбрасывая исключение И на уровне представления - не всем категориям пользователей показывая соответствующую кнопку/ссылку/форму?
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;
}
<?php include __DIR__ . '/../header.php'; ?>
<h1>Нет прав доступа</h1>
Для доступа нужно <a href="/phplearn.my/www/users/login">Войти на сайт как админ</a>
<?php include __DIR__ . '/../footer.php'; ?>
Лучше сделайте у модели User метод isAdmin(): bool и в условии используйте его следующим образом:
if (!$this->user->isAdmin()) {
Контроллер не должен знать какие там могут быть строки в поле role у модели пользователя, он должен взаимодействовать с его интерфейсом (публичными методами).
Так пойдёт. Но через isAdmin в перспективе лучше, потому что если таких мест станет несколько, везде надо будет дублировать эту логику. В контроллерах, шаблонах, моделях надо будет писать $this->getRole() !== 'admin', то есть будет дублирование кода.
А так она уже будет реализована внутри сущности и нужно будет только вызвать готовый метод.
В модели пользователя необходимо получить его роль.
Для этого создал публичный(Правильно?!) метод
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 Для каждого исключения необходимо создавать отдельный класс/файл и его ловить во фронт-контроллере или можно и на уровне обычного контроллера-обработчика в его методе?
Кавычки одинарные. Лучше завести у пользователя метод isAdmin(), в котором выполнять это сравнение.
По умолчанию private. Если требуется в наследниках - protected. Если нужен доступ извне - public.
Можно ловить где угодно. Во фронт-контроллере ловятся либо ошибки БД, либо исключения, брошенные из контроллера. Фронт-контроллер не должен напрямую ловить ошибки, брошенные в слое модели. Как например мы бросали InvalidArgumentException, но обрабатывали его в контроллере и далее рисовали ошибку в шаблоне.
Видимо не до конца понял почему те или иные исключения мы бросаем в том или ином месте. К примеру из этого урока: почему проверку авторизованного пользователя мы бросаем в контроллере, а заполнение статьи в модели. Или это исходит из того что мы ведь в модели пытаемся создать статью поэтому и исключение там бросаем?
Да. Модель валидирует данные при своём создании. Если они некорректны, бросаем исключение уровня модели. А если неавторизованный пользователь пытается обратиться к контроллеру, где авторизация является обязательной, это уже исключительная ситуация на уровне контроллера. Со временем поймёте где какие исключения бросать, не переживайте.
ArticlesController:
Templates\errors\403:
Index:
Хорошо, но в Templates\errors\403 стоит выводить переменную error в шаблоне, а не хардкодить текст ошибки.
А для проверки того, является ли юзер админом можно создать в модели User метод isAdmin, который будет это проверять:
Так?:
User:
ArticlesController:
Templates\errors\403:
Perfect!
Ещё хотел спросить - при отображении ошибки наша шапка с ником юзера слетает. Куки живы, но отсутствует объект $user, от наличия которого у нас зависит вывод. Как бы это исправить, не могу додуматься?
На странице с ошибкой не нужны данные о пользователе. Ошибка - это ошибка, больше ничего быть там не должно.
Хорошо) Но тогда нужно сделать отдельный HeaderForErrors:
А то наш базовый предлагает войти или зарегистрироваться при выводе ошибки
Да, можно так.
А нельзя просто передать во фронт-контроллере данные user и не делать новый шаблон?
У меня тогда всё отображается и в стандартном шаблоне
Не очень хорошо фронт-контроллеру обращаться к слою бизнес-логики. Он всё же должен решать архитектурные задачи.
Создаем файл Forbidden.php и создаем шаблон 403.php
Хорошо
Добрый день! д/з:
ArticlesController.php
403.php
index.php
Отлично! У юзера можно завести отдельный метод:
Вот дошёл уже до 31 урока, вроде задачи в домашках понятны, пока получается их решить))) Но вот не совсем доходит момент, когда нужно создать отдельный метод или какой тип этот метод должен отдать) Немного не понятно где создать тот или иной метод, видимо где то недочитал или недопонял) Надо как то оптимизировать своё обучение или начинать доводить блог до ума чтобы началось включение воображения, побольше практики или вернуться к началу и перечитать все по новой )))
Все придет с практикой, не переживай.
Будем стараться) Главное что есть кому задать вопрос и получить на него правильный ответ! Спасибо за обучение!)
Пожалуйста)
Вначале сделал, а затем учел некоторые рекомендации и немного переделал:
в index добавил ловлю эксепшнов:
добавил метод isAdmin в User.php :
и в метод ArticlesController add() :
сделал отдельно headerError.php и в нем изменил это :
А для чего headerError.php? Не вижу, чтобы он где-то использовался.
забыл сюда вставить шаблон 403.php
Так ведь, то что прав нет, ещё не означает, что пользователя надо просить войти. Ну да ладно, это уже вопрос бизнес логики. В целом ок.
понял)) спасибо, возьму на заметку
Норм. Но проверку на то, что юзер админ стоит проверять на уровне модели. Для этого стоит завести в ней метод isAdmin(): bool
User.php
ArticlesController.php
index.php
403.php
Имена методов пишутся с маленькой буквы.
В остальном все хорошо.
Отлично
User:
ArticlesController:
index.php:
403.php:
Супер
А как быть, если запрос через ajax и ответ нужно получить в json?
ArticlesController.php:
Насколько это правильное решение?
Или лучше через исключение, но как тогда это реализовать?
И ещё вопрос, как сделать при ловле исключения UnauthorizedException редирект Header('Location: ...) ?
Это норм решение.
По поводу редиректа при исключении при работе с API - звучит во-первых странно. Во-вторых, ваш вопрос содержит ответ. При ловле исключения отправлять заголовок с помощью header'а location. Не знаю, что еще добавить.
Спасибо за подсказку.
Наверное стоило точнее объяснить.
С учётом того, что у нас index.php ловится(как в уроках) исключение:
Пытаюсь сделать добавление новости через jquery ajax отправку формы.
Насколько правилен с точки зрения mvc такой подход и можно ли так работать с исключениями?
Прям так не очень. Лучше сделать ещё отдельные исключения для API. Например, делаете ещё APIUnauthorizedException, и его ловите во фронт-контроллере, и там уже делаете:
А в основном контроллере ловите доменное исключение, и преобразуете в исключение API:
Понял) Спасибо!
Но то, что содержимое методов контроллера ArticlesController(add, edit, view) сразу идёт в блоке try/catch, то есть полностью построены на исключениях, как в моём примере выше - это нормальная практика?
Да, обрабатывать в контроллере исключения уровня модели - это абсолютно нормально.
Допустим!
Но как разруливать ситуацию, если в разных контроллерах может быть три типа действий при ловле исключения UnauthorizedExpection:
1.
2.
3.
Тогда лучше убрать ловлю исключения UnauthorizedException из index.php ?
Для НЕ API можно бросать другое исключение, которое будет обрабатываться по-другому.
Спасибо за урок!
ArticlesController.php
index.php
templates/errors/403.php
Отлично
Можно было и функционал добавления статей в домашку отправить). Как-то легко пошел этот урок.
Но вопрос таки есть:
Для чего тут явно было указано true и код? По умолчанию же те же параметры?
Где вы увидели 302 по умолчанию?
По умолчанию в данном случае)). В первую очередь практически проверил, не указав ни параметр replace ни код. При выполнении данной функции в коде- проверил через инспектор кода в части обмена заголовками. Получаем код 302, хотя это не было указано в параметрах функции. Это же редирект/код 302? Код 201, 3хх ранее не устанавливали.
Ну и из документации:
У нас же в коде явный редирект?
Я не совсем понял этот момент, поэтому и вопросы.
Не очень понятны ситуации, когда будет использован параметр false.
Описание функции читал несколько раз, примеры пользователей тоже, но пока не въехал.
Ок, не знал что при передаче в заголовке Location будет автоматически передан код 302.
false может передаваться когда нужно вернуть в ответе несколько заголовков с одним именем, но разными значениями. Для чего так делать - не знаю, на практике ни разу не сталкивался.
Домашка
Лучше сразу строго равенство использовать: ===
В остальном всё отлично
ArticlesController.php
User.php
403.php
index.php
Отлично
ArtilesController
Article
Отлично. И лучше сделать у пользователя метод isAdmin(), чтобы статья не занималась сравнением роли со строкой. Она не должна знать о том, как правильно пишется роль пользователя.
ArticlesController.php:
User.php:
index.php:
403.php:
Отлично!
User.php
Article.php
index.php
403.php
Сначала сделал проверку админа через getRole, после зашёл в комментарии, увидел, что выполнено изящнее, много где может пригодиться эта функция, и скопипастил всё нахрен :D
Пользователю не надо писать "Ошибка 403"
В остальном ОК!
ArticlesController
index.php
403.php
Ок. Только проблема с форматированием. Делайте отступы как в уроках. Для этого в шторме можно нажать Ctrl+Alt+L
Можно сделать у юзера метод isAdmin(): bool
src\MyProject\Controllers\ArticlesController.php
src\MyProject\Models\Users\User.php
www\index.php
templates\errors\403.php
При выводе ошибки в header.php нет $user и он отображается как для неавторизованного пользователя, в комментариях написано что строка со сведениями авторизации ненужна, сделал проверку:
templates\header.php
Лучше в самом начале тогда проверить что если пустой, то вот вам исключение.
Почему в шаблоне $vars? И с чего взяли, что всегда будет ключ error? Это не так, тут будет ошибка уровня NOTICE.
Не понял, в ArticlesController.php вроде все идет правильно:
1) Если не авторизован - исключение авторизации
2) Если не админ - исключение неАдмин
3) Если не пустой $_POST - createFromArray()
3.1) Если прилетело исключение из createFromArray() - выведем ошибку на этой же странице
4) Переходим на созданную статью.
Исключение ForbiddenException вызывает в index.php $view->renderHtml:
src\MyProject\View\View.php
Передает в renderHtml массив $vars со значением error => $e->getMessage(), его я и проверяю, при вызове renderHtml для других страниц (не содержащих ошибки) в $vars не будет ключа error, и в шапке выведется строка "Привет, $username | Выйти" или "Войи | Зарегистрироваться".
Аа, по первому пункту ок, вы просто метод до конца не дописали в комментариях.
По второму пункту - мы специально делаем extract($vars) чтобы не обращаться по ключам.
Если ключа error не будет, то тут
будет ошибка. Потому что происходит обращение к несуществующему ключу. Сначала нужно убедиться, что ключ там вообще есть.
Понятно, сделал проще, после extract($vars) ключи не нужны:
templates\header.php
Так норм
src/MyProject/Models/Users/User.php
src/MyProject/Controllers/ArticlesController.php
templates/errors/403.php
www/index.php
Всё работает, но как быть с юзером в шаблоне ошибки?
Можно сразу сделать юзеру метод isAdmin(): bool
У вас же эта проблема решена уже
Выше вы говорили, что так нехорошо делать.
А что именно я говорил?
Не очень хорошо фронт-контроллеру обращаться к слою бизнес-логики. Он всё же должен решать архитектурные задачи.
Это правда, но по-другому тут никак. Увы, правила иногда приходится нарушать. Это называется костыли.
ArticlesController.php
index.php Ловлю исключение
templates\errors\403.php
ForbiddenException.php
У меня такой вопрос, в каком файле писать use НеймспейсИсключения когда его выбрасываем.
К примеру:
TestClass.php - здесь бросаем исключение
test.php - а здесь ловим
Подключать в обоих файлах или в том в котором ловим или выбрасываем?
Отлично
Подключать стоит везде. Исключение - фронт-контроллер и конфиги. Там use только усложняет поиск нужного класса.
Недостаточно передавть в метод add только id юзера? Зачем весь объект передавать?
В подобных случаях нужно делать И исключение возможности осуществления функционала на уровне кода - выбрасывая исключение И на уровне представления - не всем категориям пользователей показывая соответствующую кнопку/ссылку/форму?
Так меньше вероятность того, что вместо реально существующего пользователя прилетит какая-то ерунда.
Ответ на ваш второй вопрос - конечно же да.
?
Норм! Можно сразу метод у юзера сделать isAdmin(): bool
?
Отлично
User.php
ArticlesController
index.php
403.php
Мой вариант домашки, после её выполнения посмотрела комментарии и увидела, что более правильно будет делать проверку isAdmin на уровне модели.
Можно у модели юзера сразу метод сделать isAdmin(): bool
Уже увидела в комментах после выполнения домашки, спасибо)
домашка
ArticlesController.php
User.php
index.php
403.php
Лучше сделайте у модели User метод isAdmin(): bool и в условии используйте его следующим образом:
Контроллер не должен знать какие там могут быть строки в поле role у модели пользователя, он должен взаимодействовать с его интерфейсом (публичными методами).
В остальном всё отлично
тогда код будет таким
User.php
ArticlesController.php
сложнааа)
А почему так не пойдёт, а именно через метод isAdmin?
Так пойдёт. Но через isAdmin в перспективе лучше, потому что если таких мест станет несколько, везде надо будет дублировать эту логику. В контроллерах, шаблонах, моделях надо будет писать $this->getRole() !== 'admin', то есть будет дублирование кода.
А так она уже будет реализована внутри сущности и нужно будет только вызвать готовый метод.
User.php
ArticlesController.php
index.php
ForbiddenException.php
403.php
Отлично
Мой вариант решения домашнего задания!
Создал шаблон 403.php
ForbiddenException.php
в index.php
В модели пользователя необходимо получить его роль.
Для этого создал публичный(Правильно?!) метод
User.php
ArticlesController.php
обрабатывает проверку роли пользователя в том же методе.
Остались открытыми вопросы:
?1 Как определить где какой метод в контроллере и модели должне иметь тот или иной модификатор доступа?
?2 Для каждого исключения необходимо создавать отдельный класс/файл и его ловить во фронт-контроллере или можно и на уровне обычного контроллера-обработчика в его методе?
Спасибо за урок, ответы!
=>
Кавычки одинарные. Лучше завести у пользователя метод isAdmin(), в котором выполнять это сравнение.
Видимо не до конца понял почему те или иные исключения мы бросаем в том или ином месте. К примеру из этого урока: почему проверку авторизованного пользователя мы бросаем в контроллере, а заполнение статьи в модели. Или это исходит из того что мы ведь в модели пытаемся создать статью поэтому и исключение там бросаем?
Да. Модель валидирует данные при своём создании. Если они некорректны, бросаем исключение уровня модели. А если неавторизованный пользователь пытается обратиться к контроллеру, где авторизация является обязательной, это уже исключительная ситуация на уровне контроллера. Со временем поймёте где какие исключения бросать, не переживайте.
index.php
errors/403.php
Controllers/ArticlesController.php
Users/User.php
Exceptions/Frobidden.php
Отлично, только лучше дописать в названии класса исключения инфу о том что это исключение: ForbiddenException