Этот урок набрал набрал достаточно большое количество
комментариев и дальнейшее его комментирование отключено.
Если вы хотели убедиться в правильности выполнения ДЗ или у вас возник вопрос по уроку,
посмотрите ранее добавленные комментарии, кликнув по кнопке ниже. Скорее всего вы найдете там то, что искали.
Если это не помогло - задайте вопрос в чате в телеграме - https://t.me/php_zone
Warning: require_once(E:\OSPanel\domains\myproject.loc\www/../src/http\Exception\InvalidArgumentException.php): failed to open stream: No such file or directory in E:\OSPanel\domains\myproject.loc\www\index.php on line 16
Fatal error: require_once(): Failed opening required 'E:\OSPanel\domains\myproject.loc\www/../src/http\Exception\InvalidArgumentException.php' (include_path='.;e:/ospanel/modules/php/PHP-7.2-x64;e:/ospanel/modules/php/PHP-7.2-x64/PEAR/pear') in E:\OSPanel\domains\myproject.loc\www\index.php on line 16
Подскажи, пожалуйста, в чем может быть проблема? Никаких typo в коде нет, не могу понять что не так
Возникает на этапе обработки исключения в UsersController (в уроке это когда мы делали валидацию на наличие данных в nickname, email, password) при нажатии на submit
Можно и так, но вы должны понимать: можно (и нужно) просто добавить его через use в том классе, в котором оно бросается и где оно ловится. И это относится не только к исключениям, но и вообще к любым классам.
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) {
$this->view->renderHtml('users/signUpSuccessful.php');
return;
}
}
$this->view->renderHtml('users/signUp.php');
}
После всех условий, мы рендерим страницу. Дело в том, что у меня она рендерится дважды. А без последнего рендеринга всё ок. Что это может быть? Либо он и не нужен, а я туплю?
Warning: ReflectionObject::__construct() expects parameter 1 to be object, null given in E:\Programs\OSPanel\domains\dz.ru\src\MyProject\Models\ActiveRecordEntity.php on line 108
Fatal error: Uncaught Error: Internal error: Failed to retrieve the reflection object in E:\Programs\OSPanel\domains\dz.ru\src\MyProject\Models\ActiveRecordEntity.php:109 Stack trace: #0 E:\Programs\OSPanel\domains\dz.ru\src\MyProject\Models\ActiveRecordEntity.php(109): ReflectionClass->getProperties() #1 E:\Programs\OSPanel\domains\dz.ru\src\MyProject\Models\ActiveRecordEntity.php(102): MyProject\Models\ActiveRecordEntity->refresh() #2 E:\Programs\OSPanel\domains\dz.ru\src\MyProject\Models\ActiveRecordEntity.php(60): MyProject\Models\ActiveRecordEntity->insert(Array) #3 E:\Programs\OSPanel\domains\dz.ru\src\MyProject\Models\Users\User.php(90): MyProject\Models\ActiveRecordEntity->save() #4 E:\Programs\OSPanel\domains\dz.ru\src\MyProject\Controllers\UsersController.php(22): MyProject\Models\Users\User::signUp(Array) #5 E:\Programs\OSPanel\domains\dz.ru\www\index.php(29): MyProject\Controllers\UsersController->signUp() #6 {main} thrown in E:\Programs\OSPanel\domains\dz.ru\src\MyProject\Models\ActiveRecordEntity.php on line 109
Помогите пожалуйста разобраться с этой ошибкой
строка 108 - это в функции refresh(), а именно
$reflector = new \ReflectionObject($objectFromDb);
почему то на эту строку ругается при регистрации пользователя.
Проверял запрос в бд, все верно, В остальных файлах тоже все норм. С чем может связано?
Я так понимаю, что в данном случае это равносильно:
action = ""
Смущает сама форма записи - тут и не название скрипта и перемешано register c signUp, может хотя бы для примера нужно было оставить одноименные названия - и в названии файла шаблона и в пути - или register или signUp. До этого встречались только названия файлов со скриптами в этом атрибуте, ну или путь к этому файлу. В общем - относительно этого момента получилась каша в голове... Можно прояснить этот момент?
2. проверка ника на корректность.
if (!preg_match('/[a-zA-Z0-9]+/', $userData['nickname'])) {
throw new InvalidArgumentException('Nickname может состоять только из символов латинского алфавита и цифр');
}
Что-то меня смущает, что никто не задал этот вопрос в комментариях..
Я ошибаюсь, или действительно эта проверка не корректная? Мне кажется, что должно быть в регулярке вот так:
/^[a-zA-Z0-9]+$/
3.if ($result === [])
public static function findOneByColumn(string $columnName, $value): ?self
{
$db = Db::getInstance();
$result = $db->query(
'SELECT * FROM `' . static::getTableName() . '` WHERE `' . $columnName . '` = :value LIMIT 1;',
[':value' => $value],
static::class
);
if ($result === []) {
return null;
}
return $result[0];
}
Тут же равнозначно проверять на null или empty? Почему в уроке именно такая проверка?
4.Регистрозависимая проверка имени пользователя.
Собственно изначально - корректно ли в контексте иметь регистрозависимые ники? С другой стороны, ситуации разные есть, и касаться это может не только ников. Поэтому второй вопрос - на какой стороне необходимо(оптимальнее) озадачиваться этим нюансов - в БД делать регистрозависимые поля или указывать при запросе в БД?
Мой совет - не оставляйте action пустым, это поможет избежать ошибок. По поводу нэйминга - да, пожалуй, так будет лучше. Каши в голове у вас быть не должно. Имя шаблона указывается при рендеринге и никак не связано с роутом. Оно может быть каким угодно. Делать его таким же как и роут - решать вам.
Отлично! Глаз-алмаз) Исправил урок, спасибо.
Нет, с null сравнивать некорректно. ->query вернет массив подходящих записей. Если их нет, значит будет пустой массив, и только так.
Тут уж как вам хочется =) Разницы с точки зрения технических ограничений нет.
Слышал, что для валидации нужен отдельный класс валидации, он и кидает исключения, а в модели хранятся только массив с правилами валидации. Но смотря на код, я реально не вижу, чем такой подход хуже. Ну есть у нас 10 if'ов, ну и что, зато все понятно и наглядно. А больше и представить себе сложно, на среднем сайте максимум и есть 4 формы, и для них отдельный класс писать.. Ладно там какой-то сайт с опросами или анкетами, там ладно, валидатор нужен. А так, что ифы, что правила валидации - одинаковое количество строк занимают, только тут сразу видно и понятно, и не надо помнить, как там это валидатор у нас работает.
Правильно я рассуждаю? Учитывая что в проекте 1-2 разработчика.
Правильно я рассуждаю? Учитывая что в проекте 1-2 разработчика.
Скорее да, чем нет. С абстракциями нужно быть аккуратнее и искать баланс, чтобы не упороться через чур, создавая классы на каждый чих. Там где можно обойтись if-ом, не стоит пытаться воткнуть какой-то модный паттерн проектирования. Не всегда это ведет к упрощению кода, зачастую наоборот.
if (empty($userData['nickname'])) {
throw new InvalidArgumentException('Не передан nickname');
}
Nickname и email проверяем через empty. Но если кому-то вздумается залогиниться как "0", то empty же провалит такую проверку Корректно ли тут проверять таким образом:?
if ('' === ($userData['nickname'])) {
throw new InvalidArgumentException('Не передан nickname');
}
2.
Метод getByColumn идентичный методу getById из прошлых уроков, осталось только передать в аргументах имя колонки и LIMIT и использовать метод, как универсальный - для получения любого нужного количества записей по любому заданному значению колонки. Уместна ли такая универсализация?
3.
Этот вопрос частично из первого. Зачем на данном этапе в методе getByColumn в запросе установлен LIMIT? Для заданных на сейчас задач - ни никнейм ни почта не могут иметь значения больше одного. БД же знает, что эти колонки уникальны? Задел на будущее?
4.
public static function signUp(array $userData): User
{
// ... тут все проверки
$user = new User();
$user->nickname = $userData['nickname'];
$user->email = $userData['email'];
$user->passwordHash = password_hash($userData['password'], PASSWORD_DEFAULT);
$user->isConfirmed = false;
$user->role = 'user';
$user->authToken = sha1(random_bytes(100)) . sha1(random_bytes(100));
$user->save();
return $user;
}
Тут вручную передаем свойства role и isConfirmed. Их же можно после установки в структуре таблицы в БД как дефолтные значения в коде уже не передавать.
5.
Уже несколько уроков подряд я вижу одну и ту же штуку. В методе query мы используем fetchAll, который вроде как не имеет никакого отношения к заполнению БД но мы метод query используем и для заполнения БД и для выборки. Я бы этого и не заметил, если бы не включил с прошлого урока штатный механизм автоматического выбрасывания исключений.
Решил вопрос т.о.:
/**
* @param string $sql
* @param array $param
* @param string $className
* @param int $isFetched
* @return null|array
* @$isFetched: 0 for inserting in DB; 1 for getting from DB
*/
public function query(string $sql, array $param = [], string $className = 'stdClass', $isFetched = 0): ?array
{
$sth = $this->pdo->prepare($sql);
$sth->execute($param);
if ($isFetched) {
return $sth->fetchAll(PDO::FETCH_CLASS, $className);
}
return null;
}
Но я так понял, что это неправильно, нужно отдельные методы query делать для получения из БД и для заполнения БД
Пройдите дополнительный урок Обработка форм в PHP и напишите ответом на этот комментарий, как следует поступить.
А в чем проблема?
Да, более гибкое решение
Можно, но это размазывание логики приложения по базе. Предпочитаю так не делать.
Ок, в принципе решение рабочее. Делать или нет отдельный метод - это уже на ваш выбор. В целом выглядит логичнее отдельный метод для изменений. Потому что ему не нужен аргумент $className
Пройдите дополнительный урок Обработка форм в PHP и напишите ответом на этот комментарий, как следует поступить.
Проверяем на isset:
if (!isset($userData['nickname'])) {
throw new InvalidArgumentException('Nickname is not passed');
}
Попутно с этой нужно решить еще одну загвоздку - либо не разрешать пользователю использовать никнейм "0"(если кому то бы взбрело в голову), либо не фильтровать этот элемент массива при подготовке данных для записи в БД. Запретить пользователю конечно проще - например установить длину никнейма = минимум 3 символа.
if (!preg_match('~^[A-Za-z0-9]{3,}~', $userData['nickname'])) {
throw new InvalidArgumentException('Nickname must be of more than three only numbers and latin letters');
}
А в чем проблема?
Проблемы нет, просто добавлен еще один практически идентичный на 99% метод. Я набрался наглости и сделал из этих методов один, добавив один параметр на вход. Почему-то интуитивно тянет к агрегации. Как определять грань - чтобы и не сильно укрупнять, нагромождать методы и в то же время не слишком упрощать, разбивая методы на миллион отдельных функций на каждый чих.
Можно, но это размазывание логики приложения по базе. Предпочитаю так не делать.
Получается, что даже, если метод save завершится неудачей, в usercController все равно вернется $user без всяких условий - записался он в БД или нет?
Я для этого в usersController сделал такую проверку:
if ($user->getId() !== null) {
$message = 'New user '.$user->getNickname().' created';
$this->view->renderHtml('successful/successful.php', ['message' => $message]);
return;
} else {
$message = 'Something went wrong';
$this->view->renderHtml('unsuccessful/unsuccessful.php', ['message' => $message]);
return;
}
Ну, можно еще дополнительно проверять, является ли $user объектом класса User.
Или в классе User проверять, есть ли у объекта id и в зависимости от этого возвращать в контроллер null или объект, а в контроллере уже проверять $user, который вернулся - это null или объект класса User.
Попутно с этой нужно решить еще одну загвоздку - либо не разрешать пользователю использовать никнейм "0"(если кому то бы взбрело в голову), либо не фильтровать этот элемент массива при подготовке данных для записи в БД. Запретить пользователю конечно проще - например установить длину никнейма = минимум 3 символа.
Либо фильтровать массив только по null.
Тогда в БД вообще не делать дефолтных значений?
Тут каждый решает сам. Как минимум id должен быть дефолтным.
Проще добавить if с проверкой значения из $user->save(). Если не получилось - кидать исключение.
Проще добавить if с проверкой значения из $user->save(). Если не получилось - кидать исключение.
Я сразу и кинулся так делать, но метод save, как и insert и update ничего же не возвращает. Или речь о том, что я писал выше:
в классе User проверять, есть ли у объекта id и в зависимости от этого возвращать в контроллер null или объект, а в контроллере уже проверять $user, который вернулся - это null или объект класса User.
Только не null возвращать в контроллер и там обрабатывать проверку, а кидать исключение в классе User а в случае успеха - возвращать объект юзера в контроллер?
Ну и кроме как ПРОЩЕ - еще вопрос - УМЕСТНО ЛИ в контроллере обращаться к свойствам объекта и в контроллере проверять их?
И более глобальный вопрос относительно результатов работы методов insert/update/delete: это методы, которые не возвращают значений.
Если вставку и удаление можно отслеживать через id объекта с которым производим соответствующие действия, как контролировать update ,при выполнении которого id не меняется? Тут же тоже ситуация - например обновляем статью. Вызвали из контроллера метод save в ActiveRecordEntity, а успешно выполнился этот запрос в БД или нет - нигде не проверяется. Для этого случая у меня в качестве решения напрашивается только - встроенный механизм выбрасывания исключений. Он сам обработает корректность запроса в БД и в случае чего выбросит исключение. Или я неправильно мыслю?
Я сразу и кинулся так делать, но метод save, как и insert и update ничего же не возвращает.
А можно сделать, чтобы возвращал?
Ну и кроме как ПРОЩЕ - еще вопрос - УМЕСТНО ЛИ в контроллере обращаться к свойствам объекта и в контроллере проверять их?
Уместно, но лучше, как я написал выше, реализовать через исключения. Потому что неудачная попытка сохранить валидный объект - это исключительная ситуация.
Он сам обработает корректность запроса в БД и в случае чего выбросит исключение. Или я неправильно мыслю?
А что в документации по этому поводу говорится?
Вообще вы на уровне кода не должны допускать ошибочных запросов. Нужно тщательно проверять что летит в базу. Для этого используются параметризованные запросы и приведение типов к нужному формату. Ну и в процессе разработки вы же видите, работает запрос или нет. Если работает, то с чего ему вдруг начать ломаться?
Так любой пользовательский метод можно заставить что-то возвращать но нужен какой-то индикатор результата его выполнения.
Для метода save нужно наверное получить результаты вызываемых им методов insert|update.
А эти методы в свою очередь так же должны отдать результат своей работы. Например в методе insert должны успешно выполниться И запрос в БД И получение id И обновление текущего объекта(что тоже нужно проконтролировать = получить результат выполнения метода refresh), у метода delete должны успешно выполниться И запрос в БД и обнуление id у текущего объекта.
Можно проверять по очереди успешность выполнения этих команд и если хоть какая-то из них не выполнится - возвращать false или на каждом углу выбрасывать исключения, но вроде как то слишком утяжеляется/усложняется метод. выглядит как-то неуклюже. Будет работать, но похоже на какой-то мегакостыль.. А как по другому проверить сразу результат выполнения всего метода - не придумал.
Проще - как индикатор успешности методов вставки/удаления использовать проверку у объекта свойства id, а для метода update сделать проверку на успешность запроса в БД и в случае неудачи либо возвращать false и проверять этот результат выше, там где вызов метода был, либо если нужно - выбрасывать исключение
Ну и в процессе разработки вы же видите, работает запрос или нет.
В том то и дело, что не всегда, у меня как раз из-за этого и появились эти "идеи".
Уже не первый раз сталкиваюсь при работе с БД - вроде нет никаких явных ошибок в логике кода, как результат на экране - одобрительное сообщение - все ОК, а по факту в БД ничего не изменилось. А все из-за того, что запрос то ушел, но не отработал успешно, а обратной связи - никакой - и начинаются поиски, в каком месте ошибка. А так - можно иметь как дополнительный инструмент контроля.
Вот, как в уроке - вижу на экране "Новый юзер создан". Смотрю в БД, а его нет. Метод save отрабатывает, никаких ошибок нет, на экране все ОК - юзер якобы создан, но в БД его нет. После долгих поисков обнаруживается, что свойство никнейм = "0" отфильтровалось где-то на этапе подготовки данных а в БД нет дефолтного значения для имени юзера.
А что в документации по этому поводу говорится?
А в каком разделе документации об этом может говориться?
Можно проверять по очереди успешность выполнения этих команд и если хоть какая-то из них не выполнится - возвращать false или на каждом углу выбрасывать исключения, но вроде как то слишком утяжеляется/усложняется метод. выглядит как-то неуклюже.
Именно так выглядит корректная обработка исключений в серьезных проектах.
Метод execute может возвращать true/false, либо кидать исключения при выборе режима PDO::ERRMODE_EXCEPTION. Исключения - более детальный вариант, но требует больше кода для корректной их обработки. Какой вариант выбрать - решать вам. На работе точно будут исключения, так что, наверное, лучше их сразу и использовать.
Спасибо большое за урок.
Много полезного для себя приобрел.
Продвинулся и закрепил на практике понимание исключений.
В качестве практики и "красоты" немного адаптировал метод модели и вывод резульtата проверок
User.php
public static function signUp(array $userData)
{
var_dump($userData);
echo("<br>signUp start");
$errors = [];
if (empty($userData['nickname'])) {
$errors[] = 'Не передан nickname';
}
if (!preg_match('/^[a-zA-Z0-9]+$/', $userData['nickname'])) {
$errors[] = 'Nickname может состоять только из символов латинского алфавита и цифр';
}
if (static::findOneByColumn('nickname', $userData['nickname']) !== null) {
$errors[] = 'Пользователь с таким nickname уже существует';
}
if (empty($userData['email'])) {
$errors[] = 'Не передан email';
}
if (!filter_var($userData['email'], FILTER_VALIDATE_EMAIL)) {
$errors[] = 'Email некорректен';
}
if (static::findOneByColumn('email', $userData['email']) !== null) {
$errors[] = 'Пользователь с таким email уже существует';
}
if (empty($userData['password'])) {
$errors[] = 'Не передан password';
}
if (mb_strlen($userData['password']) < 8) {
$errors[] = 'Пароль должен быть не менее 8 символов';
}
if (count($errors)>0)
{
$error = implode("<br>", $errors);
echo($error);
throw new InvalidArgumentException($error);
}
echo("Все проверки успешно пройдены!");
$user = new User();
$user->nickname = $userData['nickname'];
$user->email = $userData['email'];
$user->passwordHash = password_hash($userData['password'], PASSWORD_DEFAULT);
$user->isConfirmed = false;
$user->role = 'user';
$user->authToken = sha1(random_bytes(100)) . sha1(random_bytes(100));
$user->save();
echo("<br>signUp stop");
return $user;
}
Хорошо, пусть будет для себя. Но на проверке ДЗ его уже быть не должно. Здесь должен быть рабочий код без отладочной информации, это же уже продвинутый курс, представляйте как будто код на работе уже пишете)
Уже было в комментариях, но там была проблема в разных названиях столбцов в таблице и свойств в объекте. У меня код также упал на приватном методе refresh(), который вызывается методом > $user->save();
$objectFromDb = static::getById($this->id);
$reflector = new \ReflectionObject($objectFromDb);
Запись в БД не создалась, в переменную $objectFromDb записался null, а не объект. В конструктор ReflectionObject попадает null и код падает.
Проблема оказалась здесь: > $user->isConfirmed = false;
Поле is_confirmed в таблице users не принимает значение false. При создании таблицы для столбца is_confirmed мы указывали тип BOOLEAN, но этот тип автоматически конвертируется в TINYINT. Стало быть может принимать только целые числа.
А вот так все получилось:
Да вполне норм решение, хотя конечно лучше автоматизировать это и не делать каждый раз на уровне сущности. Ну либо просто вместо bool юзать 0 и 1 на уровне кода)
В PHP уже есть класс InvalidArgumentException.
Понятно, что различить "стандартный" класс и наш можно по полному имени.
Но, наверное, это не очень хорошо?
Подскажи, пожалуйста, в чем может быть проблема? Никаких typo в коде нет, не могу понять что не так
Возникает на этапе обработки исключения в UsersController (в уроке это когда мы делали валидацию на наличие данных в nickname, email, password) при нажатии на submit
полный путь до исключения (неймспейс + имя класса) не прописан в классе через use. скорее всего, даже шторм выделяет цветом неопределенный класс.
В контроллере есть use MyProject\Exceptions\InvalidArgumentException, если ты об этом
Оказалось, что нужно было бросать исключение вот так:
Можно и так, но вы должны понимать: можно (и нужно) просто добавить его через use в том классе, в котором оно бросается и где оно ловится. И это относится не только к исключениям, но и вообще к любым классам.
Подскажите, пожалуйста
После всех условий, мы рендерим страницу. Дело в том, что у меня она рендерится дважды. А без последнего рендеринга всё ок. Что это может быть? Либо он и не нужен, а я туплю?
Код выглядит абсолютно корректным. Попробуйте с помощью отладчика посмотреть.
У меня тоже самое. Почему-то такое часто возникает. Если условие поменять, то все отработает.
Смотрите отладчиком. Магии тут никакой нет)
На строке 108 ожидается объект, а прилетает null.
строка 108 - это в функции refresh(), а именно
$reflector = new \ReflectionObject($objectFromDb);
почему то на эту строку ругается при регистрации пользователя.
Проверял запрос в бд, все верно, В остальных файлах тоже все норм. С чем может связано?
В $objectFromDb - null. Дебажьте, разбирайтесь.
спасибо большое!
оказалось у меня в таблице и в классе другие названия столбцов.
Весь день убил, но зато код почти наизусть выучил :D
Красава, что разобрался! =)
Один из самых динамических уроков, спасибо за вашу работу :3
Пожалуйста) Рад, что понравилось)
Присоединяюсь! Отличный урок, спасибо!
Спасибо за урок! Подскажите, пожалуйста, для чего в методе findOneByColumn в sql-запросе мы устанавливаем LIMIT 1?
Чтобы ограничить выборку единственным значением.
Есть несколько вопросов:
1. путь в атрибуте action в шаблоне для регистрации:
Я так понимаю, что в данном случае это равносильно:
Смущает сама форма записи - тут и не название скрипта и перемешано register c signUp, может хотя бы для примера нужно было оставить одноименные названия - и в названии файла шаблона и в пути - или register или signUp. До этого встречались только названия файлов со скриптами в этом атрибуте, ну или путь к этому файлу. В общем - относительно этого момента получилась каша в голове... Можно прояснить этот момент?
2. проверка ника на корректность.
Что-то меня смущает, что никто не задал этот вопрос в комментариях..
Я ошибаюсь, или действительно эта проверка не корректная? Мне кажется, что должно быть в регулярке вот так:
3. if ($result === [])
Тут же равнозначно проверять на null или empty? Почему в уроке именно такая проверка?
4. Регистрозависимая проверка имени пользователя.
Собственно изначально - корректно ли в контексте иметь регистрозависимые ники? С другой стороны, ситуации разные есть, и касаться это может не только ников. Поэтому второй вопрос - на какой стороне необходимо(оптимальнее) озадачиваться этим нюансов - в БД делать регистрозависимые поля или указывать при запросе в БД?
Оно же должно быть уникальным это значение. Поэтому больше чем одно и не выведет)
Слышал, что для валидации нужен отдельный класс валидации, он и кидает исключения, а в модели хранятся только массив с правилами валидации. Но смотря на код, я реально не вижу, чем такой подход хуже. Ну есть у нас 10 if'ов, ну и что, зато все понятно и наглядно. А больше и представить себе сложно, на среднем сайте максимум и есть 4 формы, и для них отдельный класс писать.. Ладно там какой-то сайт с опросами или анкетами, там ладно, валидатор нужен. А так, что ифы, что правила валидации - одинаковое количество строк занимают, только тут сразу видно и понятно, и не надо помнить, как там это валидатор у нас работает.
Правильно я рассуждаю? Учитывая что в проекте 1-2 разработчика.
Скорее да, чем нет. С абстракциями нужно быть аккуратнее и искать баланс, чтобы не упороться через чур, создавая классы на каждый чих. Там где можно обойтись if-ом, не стоит пытаться воткнуть какой-то модный паттерн проектирования. Не всегда это ведет к упрощению кода, зачастую наоборот.
Еще немного вопросов:
1.
Nickname и email проверяем через empty. Но если кому-то вздумается залогиниться как "0", то empty же провалит такую проверку Корректно ли тут проверять таким образом:?
2.
Метод getByColumn идентичный методу getById из прошлых уроков, осталось только передать в аргументах имя колонки и LIMIT и использовать метод, как универсальный - для получения любого нужного количества записей по любому заданному значению колонки. Уместна ли такая универсализация?
3.
Этот вопрос частично из первого. Зачем на данном этапе в методе getByColumn в запросе установлен LIMIT? Для заданных на сейчас задач - ни никнейм ни почта не могут иметь значения больше одного. БД же знает, что эти колонки уникальны? Задел на будущее?
4.
Тут вручную передаем свойства role и isConfirmed. Их же можно после установки в структуре таблицы в БД как дефолтные значения в коде уже не передавать.
5.
Уже несколько уроков подряд я вижу одну и ту же штуку. В методе query мы используем fetchAll, который вроде как не имеет никакого отношения к заполнению БД но мы метод query используем и для заполнения БД и для выборки. Я бы этого и не заметил, если бы не включил с прошлого урока штатный механизм автоматического выбрасывания исключений.
Решил вопрос т.о.:
Но я так понял, что это неправильно, нужно отдельные методы query делать для получения из БД и для заполнения БД
Проверяем на isset:
Попутно с этой нужно решить еще одну загвоздку - либо не разрешать пользователю использовать никнейм "0"(если кому то бы взбрело в голову), либо не фильтровать этот элемент массива при подготовке данных для записи в БД. Запретить пользователю конечно проще - например установить длину никнейма = минимум 3 символа.
Проблемы нет, просто добавлен еще один практически идентичный на 99% метод. Я набрался наглости и сделал из этих методов один, добавив один параметр на вход. Почему-то интуитивно тянет к агрегации. Как определять грань - чтобы и не сильно укрупнять, нагромождать методы и в то же время не слишком упрощать, разбивая методы на миллион отдельных функций на каждый чих.
Тогда в БД вообще не делать дефолтных значений?
И еще один момент:
Получается, что даже, если метод save завершится неудачей, в usercController все равно вернется $user без всяких условий - записался он в БД или нет?
Я для этого в usersController сделал такую проверку:
Ну, можно еще дополнительно проверять, является ли $user объектом класса User.
Или в классе User проверять, есть ли у объекта id и в зависимости от этого возвращать в контроллер null или объект, а в контроллере уже проверять $user, который вернулся - это null или объект класса User.
Либо фильтровать массив только по null.
Тут каждый решает сам. Как минимум id должен быть дефолтным.
Проще добавить if с проверкой значения из $user->save(). Если не получилось - кидать исключение.
Я сразу и кинулся так делать, но метод save, как и insert и update ничего же не возвращает. Или речь о том, что я писал выше:
Только не null возвращать в контроллер и там обрабатывать проверку, а кидать исключение в классе User а в случае успеха - возвращать объект юзера в контроллер?
Ну и кроме как ПРОЩЕ - еще вопрос - УМЕСТНО ЛИ в контроллере обращаться к свойствам объекта и в контроллере проверять их?
И более глобальный вопрос относительно результатов работы методов insert/update/delete: это методы, которые не возвращают значений.
Если вставку и удаление можно отслеживать через id объекта с которым производим соответствующие действия, как контролировать update ,при выполнении которого id не меняется? Тут же тоже ситуация - например обновляем статью. Вызвали из контроллера метод save в ActiveRecordEntity, а успешно выполнился этот запрос в БД или нет - нигде не проверяется. Для этого случая у меня в качестве решения напрашивается только - встроенный механизм выбрасывания исключений. Он сам обработает корректность запроса в БД и в случае чего выбросит исключение. Или я неправильно мыслю?
А можно сделать, чтобы возвращал?
Уместно, но лучше, как я написал выше, реализовать через исключения. Потому что неудачная попытка сохранить валидный объект - это исключительная ситуация.
А что в документации по этому поводу говорится?
Вообще вы на уровне кода не должны допускать ошибочных запросов. Нужно тщательно проверять что летит в базу. Для этого используются параметризованные запросы и приведение типов к нужному формату. Ну и в процессе разработки вы же видите, работает запрос или нет. Если работает, то с чего ему вдруг начать ломаться?
Так любой пользовательский метод можно заставить что-то возвращать но нужен какой-то индикатор результата его выполнения.
Для метода save нужно наверное получить результаты вызываемых им методов insert|update.
А эти методы в свою очередь так же должны отдать результат своей работы. Например в методе insert должны успешно выполниться И запрос в БД И получение id И обновление текущего объекта(что тоже нужно проконтролировать = получить результат выполнения метода refresh), у метода delete должны успешно выполниться И запрос в БД и обнуление id у текущего объекта.
Можно проверять по очереди успешность выполнения этих команд и если хоть какая-то из них не выполнится - возвращать false или на каждом углу выбрасывать исключения, но вроде как то слишком утяжеляется/усложняется метод. выглядит как-то неуклюже. Будет работать, но похоже на какой-то мегакостыль.. А как по другому проверить сразу результат выполнения всего метода - не придумал.
Проще - как индикатор успешности методов вставки/удаления использовать проверку у объекта свойства id, а для метода update сделать проверку на успешность запроса в БД и в случае неудачи либо возвращать false и проверять этот результат выше, там где вызов метода был, либо если нужно - выбрасывать исключение
В том то и дело, что не всегда, у меня как раз из-за этого и появились эти "идеи".
Уже не первый раз сталкиваюсь при работе с БД - вроде нет никаких явных ошибок в логике кода, как результат на экране - одобрительное сообщение - все ОК, а по факту в БД ничего не изменилось. А все из-за того, что запрос то ушел, но не отработал успешно, а обратной связи - никакой - и начинаются поиски, в каком месте ошибка. А так - можно иметь как дополнительный инструмент контроля.
Вот, как в уроке - вижу на экране "Новый юзер создан". Смотрю в БД, а его нет. Метод save отрабатывает, никаких ошибок нет, на экране все ОК - юзер якобы создан, но в БД его нет. После долгих поисков обнаруживается, что свойство никнейм = "0" отфильтровалось где-то на этапе подготовки данных а в БД нет дефолтного значения для имени юзера.
А в каком разделе документации об этом может говориться?
Именно так выглядит корректная обработка исключений в серьезных проектах.
Метод execute может возвращать true/false, либо кидать исключения при выборе режима PDO::ERRMODE_EXCEPTION. Исключения - более детальный вариант, но требует больше кода для корректной их обработки. Какой вариант выбрать - решать вам. На работе точно будут исключения, так что, наверное, лучше их сразу и использовать.
Спасибо, принял во внимание.
Тест
Этот метод будет принимает два параметра
В предложений заметил отпечатку принимает => принимать
Спасибо за урок! :D
Спасибо, исправил
Спасибо большое за урок.
Много полезного для себя приобрел.
Продвинулся и закрепил на практике понимание исключений.
В качестве практики и "красоты" немного адаптировал метод модели и вывод резульtата проверок
User.php
Нужно бросать исключение, echo здесь не нужен.
Я же бросил исключение
throw new InvalidArgumentException($error);
Эхо у меня чисто только для себя, чтобы отловить ошибки.
Хорошо, пусть будет для себя. Но на проверке ДЗ его уже быть не должно. Здесь должен быть рабочий код без отладочной информации, это же уже продвинутый курс, представляйте как будто код на работе уже пишете)
Хорошо, согласен!
Уже было в комментариях, но там была проблема в разных названиях столбцов в таблице и свойств в объекте. У меня код также упал на приватном методе refresh(), который вызывается методом > $user->save();
Запись в БД не создалась, в переменную $objectFromDb записался null, а не объект. В конструктор ReflectionObject попадает null и код падает.
Проблема оказалась здесь: > $user->isConfirmed = false;
Поле is_confirmed в таблице users не принимает значение false. При создании таблицы для столбца is_confirmed мы указывали тип BOOLEAN, но этот тип автоматически конвертируется в TINYINT. Стало быть может принимать только целые числа.
А вот так все получилось:
Вот кстати, можно подумать на тему того как автоматически маппить boolean в int при маппинге в базу и обратно.
Ну, разве что не работать со свойством напрямую, а использовать сеттеры/геттеры.
Или еще нужно подумать?
Да вполне норм решение, хотя конечно лучше автоматизировать это и не делать каждый раз на уровне сущности. Ну либо просто вместо bool юзать 0 и 1 на уровне кода)
Пришлось приводить тип к INT ( в базе тип TINYINT)
$user->isConfirmed = (int)false;
Иначе не пишется в БД.
Отлично
Для контроля входных значений мы создаём класс InvalidArgumentException, совпадающий со "стандартным".
Как-то настораживает...
Что значит совпадающий и что именно настораживает?
В PHP уже есть класс InvalidArgumentException.
Понятно, что различить "стандартный" класс и наш можно по полному имени.
Но, наверное, это не очень хорошо?
Вполне нормально