Поддержать проект
Новый комментарий

Sparkqy 29.09.2018 в 16:05
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 в коде нет, не могу понять что не так

Sparkqy 29.09.2018 в 16:08

Возникает на этапе обработки исключения в UsersController (в уроке это когда мы делали валидацию на наличие данных в nickname, email, password) при нажатии на submit

ivashkevich 30.09.2018 в 10:53

полный путь до исключения (неймспейс + имя класса) не прописан в классе через use. скорее всего, даже шторм выделяет цветом неопределенный класс.

Sparkqy 30.09.2018 в 12:11

В контроллере есть use MyProject\Exceptions\InvalidArgumentException, если ты об этом

Sparkqy 30.09.2018 в 19:41

Оказалось, что нужно было бросать исключение вот так:

if (empty($userData['nickname']))
       {
           throw new \MyProject\Exceptions\InvalidArgumentException('Не передан никнейм');
       }
ivashkevich 02.10.2018 в 21:45

Можно и так, но вы должны понимать: можно (и нужно) просто добавить его через use в том классе, в котором оно бросается и где оно ловится. И это относится не только к исключениям, но и вообще к любым классам.

ilyaOrlov 01.12.2018 в 22:11

Подскажите, пожалуйста

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

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

ivashkevich 02.12.2018 в 09:51

Код выглядит абсолютно корректным. Попробуйте с помощью отладчика посмотреть.

prognoz 24.08.2019 в 19:33

У меня тоже самое. Почему-то такое часто возникает. Если условие поменять, то все отработает.

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');
            }
        } else {
            $this->view->renderHtml('users/signUp.php');
        }

    }
ivashkevich 25.08.2019 в 13:44

Смотрите отладчиком. Магии тут никакой нет)

alepawka 07.01.2019 в 14:25
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 

Помогите пожалуйста разобраться с этой ошибкой
ivashkevich 07.01.2019 в 16:03

На строке 108 ожидается объект, а прилетает null.

alepawka 07.01.2019 в 20:02

строка 108 - это в функции refresh(), а именно
$reflector = new \ReflectionObject($objectFromDb);
почему то на эту строку ругается при регистрации пользователя.
Проверял запрос в бд, все верно, В остальных файлах тоже все норм. С чем может связано?

ivashkevich 07.01.2019 в 21:45

В $objectFromDb - null. Дебажьте, разбирайтесь.

alepawka 08.01.2019 в 12:41

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

ivashkevich 09.01.2019 в 21:27

Красава, что разобрался! =)

Iliusha99 30.07.2019 в 17:41

Один из самых динамических уроков, спасибо за вашу работу :3

ivashkevich 30.07.2019 в 18:27

Пожалуйста) Рад, что понравилось)

artemship 26.08.2019 в 16:36

Присоединяюсь! Отличный урок, спасибо!

andreskrip 11.02.2020 в 20:39

Спасибо за урок! Подскажите, пожалуйста, для чего в методе findOneByColumn в sql-запросе мы устанавливаем LIMIT 1?

ivashkevich 12.02.2020 в 13:09

Чтобы ограничить выборку единственным значением.

OneMoreTime 17.03.2020 в 18:01

Есть несколько вопросов:
1. путь в атрибуте action в шаблоне для регистрации:

<?php include __DIR__ . '/../header.php'; ?>
    <div style="text-align: center;">
        <h1>Регистрация</h1>
        <form action="/users/register" method="post">
            <label>Nickname <input type="text" name="nickname"></label>
            <br><br>
            <label>Email <input type="text" name="email"></label>
            <br><br>
            <label>Пароль <input type="password" name="password"></label>
            <br><br>
            <input type="submit" value="Зарегистрироваться">
        </form>
    </div>
<?php include __DIR__ . '/../footer.php'; ?>

Я так понимаю, что в данном случае это равносильно:

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. Регистрозависимая проверка имени пользователя.
Собственно изначально - корректно ли в контексте иметь регистрозависимые ники? С другой стороны, ситуации разные есть, и касаться это может не только ников. Поэтому второй вопрос - на какой стороне необходимо(оптимальнее) озадачиваться этим нюансов - в БД делать регистрозависимые поля или указывать при запросе в БД?

ivashkevich 18.03.2020 в 05:12
  1. Мой совет - не оставляйте action пустым, это поможет избежать ошибок. По поводу нэйминга - да, пожалуй, так будет лучше. Каши в голове у вас быть не должно. Имя шаблона указывается при рендеринге и никак не связано с роутом. Оно может быть каким угодно. Делать его таким же как и роут - решать вам.
  2. Отлично! Глаз-алмаз) Исправил урок, спасибо.
  3. Нет, с null сравнивать некорректно. ->query вернет массив подходящих записей. Если их нет, значит будет пустой массив, и только так.
  4. Тут уж как вам хочется =) Разницы с точки зрения технических ограничений нет.
jimholder37@gmail.com 11.05.2020 в 17:35

Слышал, что для валидации нужен отдельный класс валидации, он и кидает исключения, а в модели хранятся только массив с правилами валидации. Но смотря на код, я реально не вижу, чем такой подход хуже. Ну есть у нас 10 if'ов, ну и что, зато все понятно и наглядно. А больше и представить себе сложно, на среднем сайте максимум и есть 4 формы, и для них отдельный класс писать.. Ладно там какой-то сайт с опросами или анкетами, там ладно, валидатор нужен. А так, что ифы, что правила валидации - одинаковое количество строк занимают, только тут сразу видно и понятно, и не надо помнить, как там это валидатор у нас работает.
Правильно я рассуждаю? Учитывая что в проекте 1-2 разработчика.

ivashkevich 12.05.2020 в 08:07

Правильно я рассуждаю? Учитывая что в проекте 1-2 разработчика.

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

OneMoreTime 14.05.2020 в 23:14

Еще немного вопросов:
1.

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 делать для получения из БД и для заполнения БД

ivashkevich 15.05.2020 в 18:34
  1. Пройдите дополнительный урок Обработка форм в PHP и напишите ответом на этот комментарий, как следует поступить.
  2. А в чем проблема?
  3. Да, более гибкое решение
  4. Можно, но это размазывание логики приложения по базе. Предпочитаю так не делать.
  5. Ок, в принципе решение рабочее. Делать или нет отдельный метод - это уже на ваш выбор. В целом выглядит логичнее отдельный метод для изменений. Потому что ему не нужен аргумент $className
OneMoreTime 15.05.2020 в 21:16

Пройдите дополнительный урок Обработка форм в 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% метод. Я набрался наглости и сделал из этих методов один, добавив один параметр на вход. Почему-то интуитивно тянет к агрегации. Как определять грань - чтобы и не сильно укрупнять, нагромождать методы и в то же время не слишком упрощать, разбивая методы на миллион отдельных функций на каждый чих.

Можно, но это размазывание логики приложения по базе. Предпочитаю так не делать.

Тогда в БД вообще не делать дефолтных значений?


И еще один момент:

$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;

Получается, что даже, если метод 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.

ivashkevich 16.05.2020 в 12:14

Попутно с этой нужно решить еще одну загвоздку - либо не разрешать пользователю использовать никнейм "0"(если кому то бы взбрело в голову), либо не фильтровать этот элемент массива при подготовке данных для записи в БД. Запретить пользователю конечно проще - например установить длину никнейма = минимум 3 символа.

Либо фильтровать массив только по null.

Тогда в БД вообще не делать дефолтных значений?

Тут каждый решает сам. Как минимум id должен быть дефолтным.

Проще добавить if с проверкой значения из $user->save(). Если не получилось - кидать исключение.

OneMoreTime 16.05.2020 в 13:26

Проще добавить if с проверкой значения из $user->save(). Если не получилось - кидать исключение.

Я сразу и кинулся так делать, но метод save, как и insert и update ничего же не возвращает. Или речь о том, что я писал выше:

в классе User проверять, есть ли у объекта id и в зависимости от этого возвращать в контроллер null или объект, а в контроллере уже проверять $user, который вернулся - это null или объект класса User.

Только не null возвращать в контроллер и там обрабатывать проверку, а кидать исключение в классе User а в случае успеха - возвращать объект юзера в контроллер?


Ну и кроме как ПРОЩЕ - еще вопрос - УМЕСТНО ЛИ в контроллере обращаться к свойствам объекта и в контроллере проверять их?


И более глобальный вопрос относительно результатов работы методов insert/update/delete: это методы, которые не возвращают значений.
Если вставку и удаление можно отслеживать через id объекта с которым производим соответствующие действия, как контролировать update ,при выполнении которого id не меняется? Тут же тоже ситуация - например обновляем статью. Вызвали из контроллера метод save в ActiveRecordEntity, а успешно выполнился этот запрос в БД или нет - нигде не проверяется. Для этого случая у меня в качестве решения напрашивается только - встроенный механизм выбрасывания исключений. Он сам обработает корректность запроса в БД и в случае чего выбросит исключение. Или я неправильно мыслю?

ivashkevich 16.05.2020 в 14:13

Я сразу и кинулся так делать, но метод save, как и insert и update ничего же не возвращает.

А можно сделать, чтобы возвращал?

Ну и кроме как ПРОЩЕ - еще вопрос - УМЕСТНО ЛИ в контроллере обращаться к свойствам объекта и в контроллере проверять их?

Уместно, но лучше, как я написал выше, реализовать через исключения. Потому что неудачная попытка сохранить валидный объект - это исключительная ситуация.

Он сам обработает корректность запроса в БД и в случае чего выбросит исключение. Или я неправильно мыслю?

А что в документации по этому поводу говорится?

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

OneMoreTime 16.05.2020 в 14:54
А можно сделать, чтобы возвращал?

Так любой пользовательский метод можно заставить что-то возвращать но нужен какой-то индикатор результата его выполнения.
Для метода save нужно наверное получить результаты вызываемых им методов insert|update.
А эти методы в свою очередь так же должны отдать результат своей работы. Например в методе insert должны успешно выполниться И запрос в БД И получение id И обновление текущего объекта(что тоже нужно проконтролировать = получить результат выполнения метода refresh), у метода delete должны успешно выполниться И запрос в БД и обнуление id у текущего объекта.
Можно проверять по очереди успешность выполнения этих команд и если хоть какая-то из них не выполнится - возвращать false или на каждом углу выбрасывать исключения, но вроде как то слишком утяжеляется/усложняется метод. выглядит как-то неуклюже. Будет работать, но похоже на какой-то мегакостыль.. А как по другому проверить сразу результат выполнения всего метода - не придумал.

Проще - как индикатор успешности методов вставки/удаления использовать проверку у объекта свойства id, а для метода update сделать проверку на успешность запроса в БД и в случае неудачи либо возвращать false и проверять этот результат выше, там где вызов метода был, либо если нужно - выбрасывать исключение


Ну и в процессе разработки вы же видите, работает запрос или нет.

В том то и дело, что не всегда, у меня как раз из-за этого и появились эти "идеи".

Уже не первый раз сталкиваюсь при работе с БД - вроде нет никаких явных ошибок в логике кода, как результат на экране - одобрительное сообщение - все ОК, а по факту в БД ничего не изменилось. А все из-за того, что запрос то ушел, но не отработал успешно, а обратной связи - никакой - и начинаются поиски, в каком месте ошибка. А так - можно иметь как дополнительный инструмент контроля.

Вот, как в уроке - вижу на экране "Новый юзер создан". Смотрю в БД, а его нет. Метод save отрабатывает, никаких ошибок нет, на экране все ОК - юзер якобы создан, но в БД его нет. После долгих поисков обнаруживается, что свойство никнейм = "0" отфильтровалось где-то на этапе подготовки данных а в БД нет дефолтного значения для имени юзера.

А что в документации по этому поводу говорится?

А в каком разделе документации об этом может говориться?

ivashkevich 17.05.2020 в 14:51

Можно проверять по очереди успешность выполнения этих команд и если хоть какая-то из них не выполнится - возвращать false или на каждом углу выбрасывать исключения, но вроде как то слишком утяжеляется/усложняется метод. выглядит как-то неуклюже.

Именно так выглядит корректная обработка исключений в серьезных проектах.

Метод execute может возвращать true/false, либо кидать исключения при выборе режима PDO::ERRMODE_EXCEPTION. Исключения - более детальный вариант, но требует больше кода для корректной их обработки. Какой вариант выбрать - решать вам. На работе точно будут исключения, так что, наверное, лучше их сразу и использовать.

OneMoreTime 17.05.2020 в 21:22

Спасибо, принял во внимание.

studentDev Patron 16.05.2020 в 12:17

Этот метод будет принимает два параметра
В предложений заметил отпечатку принимает => принимать

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

ivashkevich 16.05.2020 в 12:33

Спасибо, исправил

SkSeMi 23.01.2021 в 18:59

Спасибо большое за урок.
Много полезного для себя приобрел.
Продвинулся и закрепил на практике понимание исключений.
В качестве практики и "красоты" немного адаптировал метод модели и вывод резуль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;
    }
ivashkevich 23.01.2021 в 20:31
            $error  = implode("<br>", $errors);
            echo($error);
            throw new InvalidArgumentException($error);

Нужно бросать исключение, echo здесь не нужен.

SkSeMi 23.01.2021 в 20:52

Я же бросил исключение
throw new InvalidArgumentException($error);

Эхо у меня чисто только для себя, чтобы отловить ошибки.

ivashkevich 23.01.2021 в 21:16

Хорошо, пусть будет для себя. Но на проверке ДЗ его уже быть не должно. Здесь должен быть рабочий код без отладочной информации, это же уже продвинутый курс, представляйте как будто код на работе уже пишете)

SkSeMi 23.01.2021 в 21:54

Хорошо, согласен!

ivan.tretiakov 12.03.2021 в 14:43

Уже было в комментариях, но там была проблема в разных названиях столбцов в таблице и свойств в объекте. У меня код также упал на приватном методе 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. Стало быть может принимать только целые числа.
А вот так все получилось:

$user->isConfirmed = 0;
ivashkevich 17.03.2021 в 16:05

Вот кстати, можно подумать на тему того как автоматически маппить boolean в int при маппинге в базу и обратно.

ivan.tretiakov 20.03.2021 в 21:33

Ну, разве что не работать со свойством напрямую, а использовать сеттеры/геттеры.

// геттер
public function isConfirmed(): bool
{
    return $this->isConfirmed === 1; 
}

// сеттер
public function setIsConfirmed(bool $value)
{
    $this->isConfirmed = $value ? 1 : 0;
}

Или еще нужно подумать?

ivashkevich 26.03.2021 в 17:29

Да вполне норм решение, хотя конечно лучше автоматизировать это и не делать каждый раз на уровне сущности. Ну либо просто вместо bool юзать 0 и 1 на уровне кода)

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