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

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 Patron 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 16.05.2020 в 12:17

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

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

ivashkevich 16.05.2020 в 12:33

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

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