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

ivashkevich 31.07.2018 в 17:33

Пример решения домашки:

    private function insert(array $mappedProperties): void
    {
        $filteredProperties = array_filter($mappedProperties);

        $columns = [];
        $paramsNames = [];
        $params2values = [];
        foreach ($filteredProperties as $columnName => $value) {
            $columns[] = '`' . $columnName. '`';
            $paramName = ':' . $columnName;
            $paramsNames[] = $paramName;
            $params2values[$paramName] = $value;
        }

        $columnsViaSemicolon = implode(', ', $columns);
        $paramsNamesViaSemicolon = implode(', ', $paramsNames);

        $sql = 'INSERT INTO ' . static::getTableName() . ' (' . $columnsViaSemicolon . ') VALUES (' . $paramsNamesViaSemicolon . ');';

        $db = Db::getInstance();
        $db->query($sql, $params2values, static::class);
        $this->id = $db->getLastInsertId();
        $this->refresh();
    }

    private function refresh(): void
    {
        $objectFromDb = static::getById($this->id);
        $reflector = new \ReflectionObject($objectFromDb);
        $properties = $reflector->getProperties();

        foreach ($properties as $property) {
            $property->setAccessible(true);
            $propertyName = $property->getName();
            $this->$propertyName = $property->getValue($objectFromDb);
        }
    }
Ilon 01.09.2018 в 14:10

чет сложна)

ivashkevich 01.09.2018 в 19:07

дебаггер в помощь)

Sparkqy 25.09.2018 в 18:19

Можно плиз краткое объяснение, как работает refresh()? :) потому что "чет сложна)" реально ахаха

ivashkevich 25.09.2018 в 22:12

Он берет версию объекта из базы, получает все его свойства.
Затем бежит в цикле по этим свойствам и:

  1. делает их публичными;
  2. читает их имя;
  3. в текущем объекте (у которого вызвали refresh) свойству с таким же именем задаёт значение из свойства, взятого у объекта из базы ($objectFromDb).
excent63 24.03.2019 в 21:00

Немного не пойму, почему он числовые значения передаёт как строковые после добавления refresh();

object(MyProject\Models\Articles\Article)[6]
  protected 'name' => string 'Новая Статья' (length=23)
  protected 'text' => string 'Новый техт' (length=19)
  protected 'authorId' => string '1' (length=1)
  protected 'createdAt' => string '2019-03-24 20:57:08' (length=19)
  protected 'id' => string '3' (length=1)
ivashkevich 25.03.2019 в 11:34

Потому что из базы так возвращаются.

excent63 25.03.2019 в 13:38

Понял, спасибо!

ivashkevich 25.03.2019 в 13:45
excent63 25.03.2019 в 13:54

О спасибо! Обязательно прочту

Boodoo 24.04.2019 в 11:08

Тяжеловато вошло....

Metey 21.07.2019 в 16:06

А так можно делать??

public function insert(array $mappedProperties): void
    {
        $filteredProperties = array_filter($mappedProperties);

        $columns = [];
        $paramsNames = [];
        $params2values = [];
        foreach ($filteredProperties as $columnName => $value) {
            $columns[] = '`' . $columnName. '`';
            $paramName = ':' . $columnName;
            $paramsNames[] = $paramName;
            $params2values[$paramName] = $value;
        }

        $columnsViaSemicolon = implode(', ', $columns);
        $paramsNamesViaSemicolon = implode(', ', $paramsNames);

        $sql = 'INSERT INTO ' . static::getTableName() . ' (' . $columnsViaSemicolon . ') 
                    VALUES (' . $paramsNamesViaSemicolon . ');';

        $db = Db::getInstance();
        $db->query($sql, $params2values, static::class); 
        $this->id = $db->getLastInsertId();
        $this->refresh();
    }

    public function refresh(): void
    {
        $objFromDb = static::getById($this->id);

        $properties = get_object_vars($objFromDb);

        foreach ($properties as $key=>$value) {
            $this->$key = $value;
        }
    }
ivashkevich 21.07.2019 в 19:15

Отлично

EugeneGrigoryev 25.01.2020 в 01:32

src/MyProject/Models/ActiveRecordEntity.php
Примерно понимал как сделать, но добиться нужного результата не получилось, поэтому подсмотрел в готовые решения :(

private function insert(array $mappedProperties): void
    {
        $columns2params = [];
        $params2values = [];
        foreach ($mappedProperties as $column => $value) {
            if ($value === null) continue;
            $columns2params[] = "{$column} = :{$column}";
            $params2values[":{$column}"] = $value;
        }
        $sql = 'INSERT INTO ' . static::getTableName() . ' SET ' . implode(', ', $columns2params);
        $db = Db::getInstance();
        $db->query($sql, $params2values, static::class);
        $this->id = $db->getLastInsertId();
        $this->refresh();
    }
    private function refresh(): void
    {
        $objectFromDb = static::getById($this->id);
        $reflector = new \ReflectionObject($objectFromDb);
        $properties = $reflector->getProperties();

        foreach ($properties as $property) {
            $property->setAccessible(true);
            $propertyName = $property->getName();
            $this->$propertyName = $property->getValue($objectFromDb);
        }
    }
ivashkevich 25.01.2020 в 14:00

Главное чтобы в итоге это решение было вам понятно и вы его запомнили, точнее, могли реализовать в дальнейшем.

andreskrip 04.02.2020 в 14:58

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

private function insert(array $mappedProperties): void
    {
        $filterProperties = array_filter($mappedProperties);
        $columns = [];
        $values = [];
        $params = [];

        foreach ($filterProperties as $column => $value) {
            $columns [] = '`' . $column . '`';
            $valueName = ':' . $column;
            $values[] = $valueName;
            $params[$valueName] = $value;
        }

        $columnsViaCommas = implode(', ', $columns);
        $valuesViaCommas = implode(', ', $values);
        $sql = 'INSERT INTO `' . static::getTableName() . '` (' . $columnsViaCommas . ') VALUES (' . $valuesViaCommas . ');';
        $db = Db::getInstance();
        $db->query($sql, $params, static::class);
        $this->id = $db->getLastInsertId();
        $this->refresh();
    }

    private function refresh(): void
    {
        $objectFromDb = static::getById($this->id);

        foreach ($objectFromDb as $property => $value) {
            $this->$property = $value;
        }

    }

Можно ли так упростить функцию refresh? Или могут быть случаи, когда перебор задаст неправильные значения?

ivashkevich 06.02.2020 в 03:07

В базе имена столбцов именуются с подчеркиваниями. В коде - camelCase. Так что нет, нельзя.

andreskrip 11.02.2020 в 17:55

Но разве в ActiveRecord прописанный магический метод __set, не занимается переименовыванием свойств в надлежащий вид?

ivashkevich 12.02.2020 в 13:08

А, сорян. Да, можно

OneMoreTime 11.03.2020 в 00:40

1. В методах insert и update по сути бОльшая часть методов содержит один и тот же код(преобразование одного массива в два), за исключением очистки массива свойств, строки запроса, ну и заключительной части в методе insert из этого урока. Можно ли как-то унифицировать эти медоды, чтобы не было повторяющегося кода в разных методах?

2.

Например, в поле createdAt должна появиться строка с датой.

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

3. Сложность решения задачи - в первую очередь из-за не знания моря(!) встроенных функций. Сам принцип получения этой информации(решения задачи) понятен, а вот реализация - посредством чего можно сделать - по аналогии с английским языком - вроде и конструкция предложения понятна/знакома, а нужные слова не знаешь.

ivashkevich 13.03.2020 в 17:16
  1. Ну если он одинаковый, то можно вынести его в отдельный метод.
  2. Чтобы отобразить дату создания записи?
  3. Учите понемногу английский.
OneMoreTime 13.03.2020 в 17:38

Учите понемногу английский.

)) Английский - это просто аналогия. А вопорс в том, что в языке очень много встроенных функций, которыми не приходилось пользоваться и даже неизвестно о их существовании. Например, дойдя уже до этого урока многие вещи просто не опробованы(из тех же операций со строками, массивами. как раз пример из этого урока: array_filter), поэтому многие вопросы решаются посредством костылей и полотнами кода. При этом, тоже не будеьтупо подряд по списку изучать все функции из документации. На это пол жизни уйдет, и не успеешь на работу устроиться))

ivashkevich 13.03.2020 в 17:40

Не переживайте. Если вы вместо array_filter напишите foreach, хуже не станет.

Dimitry 05.04.2020 в 07:33
private function insert(array $mappedProperties): void
    {
        $filterProperties = array_filter($mappedProperties);
        $columns = [];
        $values = [];
        $params = [];

        foreach ($filterProperties as $column => $value) {
            $columns [] = '`' . $column . '`';
            $valueName = ':' . $column;
            $values[] = $valueName;
            $params[$valueName] = $value;
        }

        $columnsViaCommas = implode(', ', $columns);
        $valuesViaCommas = implode(', ', $values);
        $sql = 'INSERT INTO `' . static::getTableName() . '` (' . $columnsViaCommas . ') VALUES (' . $valuesViaCommas . ');';
        $db = Db::getInstance();
        $db->query($sql, $params, static::class);
        $this->id = $db->getLastInsertId();
        $this->refresh();
    }

    private function refresh(): void
    {
        $objectFromDb = static::getById($this->id);

        foreach ($objectFromDb as $property => $value) {
            $this->$property = $value;
        }

    }

Домашка

ivashkevich 05.04.2020 в 19:18

Отлично!

Alexann 14.04.2020 в 23:12

Почему-то два раза отправилось одно и то же - удалил.

gizbreht.e@yandex.ru 14.04.2020 в 23:27

Article

public function getCreatedAt() : string
  {
    return $this->createdAt;
  }

ActiveRecordEntity

    $article = self::getById($this->id);
    $this->createdAt = $article->getCreatedAt();
ivashkevich 15.04.2020 в 11:13

Решение должно быть гибким и касаться всех полей, а не только createdAt, приведенного в качестве примера.

Общий объект ActiveRecordEntity ничего не должен знать о статьях!

Alexann 15.04.2020 в 08:05

Файл ActiveRecordEntity.php:

...
private function insert(array $mappedProperties): void
    {
   ...
        $this->id = $db->getLastInsertId();
        $object = static::getById($this->id);
        $this->createdAt = $object->getCreatedAt();
    }

И в Article.php добавить геттер getCreatedAt().

ivashkevich 15.04.2020 в 11:34

Решение должно быть гибким и касаться всех полей, а не только createdAt, приведенного в качестве примера.

Alexann 15.04.2020 в 13:11

Идея была обновить все поля, но компактного решения не нашел, поэтому вот так (наверное самое оптимальное):

...
private function insert(array $mappedProperties): void
    {
...
 $this->refresh();
    }

    private function refresh(): void
    {
        $object = static::getById($this->id);
        foreach ($object as $property => $value) {
            $this->$property = $value;
        }
    }
ivashkevich 15.04.2020 в 17:30

Ок, а благодаря чему стало возможным вот так итерировать объект?

        foreach ($object as $property => $value) {
            $this->$property = $value;
        }
Alexann 15.04.2020 в 22:23

Если я правильно понял вопрос, то потому что foreach позволяет пройти по тем свойствам объекта (как по элементам массива), которые являются видимыми и доступными. Если копнуть глубже, то с помощью интерфейса Traversable, который определяет является ли класс обходимым с использованием foreach. Я правда очень смутно это понимаю (пока что).

ivashkevich 16.04.2020 в 06:59

Отлично. Этот интерфейс позволяет... да что я вам буду рассказывать, вы можете прочитать о нем в документации. Там же есть примеры.

Fill Patron 29.04.2020 в 05:06
    private function insert(array $mappedProperties): void
    {
        ...
        $this->id = $db->getLastInsertId();
        $this->refresh();
    }

    private function refresh(): void
    {
        $lastInsertedObject = static::getById($this->id);
        $reflector = new \ReflectionObject($lastInsertedObject);
        $properties = $reflector->getProperties();

        foreach ($properties as $property) {
            $propertyName = $property->getName();
            $this->$propertyName = $lastInsertedObject->$propertyName;
        }
    }
ivashkevich 29.04.2020 в 19:17

Отлично

studentDev 11.05.2020 в 12:50
private function insert(array $mappedProperties): void
        {
            $filteredProperties = array_filter($mappedProperties); //

            $columns = [];
            $paramsNames = [];
            $params2values = [];
            foreach($filteredProperties as $columnName => $value) {
                $columns[] = '`' . $columnName . '`';
                $paramName = ':' . $columnName;
                $paramsNames[] = $paramName;
                $params2values[$paramName] = $value;
            }

            $columnsViaSemicolon = implode(', ', $columns);
            $paramsNamesViaSemicolon = implode(', ', $paramsNames);

            $sql = 'INSERT INTO ' . static::getTableName() . ' (' . $columnsViaSemicolon . ' ) VALUES (' . $paramsNamesViaSemicolon . ')';
            $db = Db::getInstance();
            $db->query($sql, $params2values, static::class);
            $this->id = $db->getLastInsertId();
            $this->refresh();
        }

        public function refresh(): void
        {
            $objects = static::getById($this->id);

            foreach($objects as $obj => $value) {
                $this->$obj = $value;
            }
        }

?

ivashkevich 12.05.2020 в 07:59

Отлично!

OneMoreTime 11.05.2020 в 18:15

1.
Почему в этом уроке название таблицы и названия столбцов в обратных апострофах, а в прошлом -нет?

2.
Почему в одном уроке запрос в БД с синтаксическим сахаром, в другом - нет? Должно же быть однообразно во всем проекте? И вообще, приемлемо ли его использование в контексте стандартов и чистого кода в PHP?

3.
В примере решения вопроса из домашки использовано получение значения через метод getValue()

$this->$propertyName = $property->getValue($objectFromDb);

Но ведь можно напрямую обратиться к свойству объекта без встроенного геттера:

$this->$propertyName = $objectFromDb->$propertyName;

В чем разница между такими способами получения значения?

ivashkevich 12.05.2020 в 08:13
  1. Можно и так и так. Это просто моё настроение, не более того)
  2. К единообразию следует стремиться. Но всё равно не получится, особенно если на проекте более одного человека. У меня вот, как видите, и в одиночку не получается, просто прошла неделя между выпуском уроков. А про какой именно сахар речь?
  3. Да, так тоже можно. У меня была логика именно в единообразии, раз начал через неё работать, значит и заканчивать через неё)
OneMoreTime 12.05.2020 в 09:51

А про какой именно сахар речь?

INSERT INTO `table` (column1, column2)  VALUES (:param1, :param2);

vs

INSERT INTO `table` SET (column1=:param1, column2=:param2);
ivashkevich 13.05.2020 в 07:13

Ок, спасибо за ответ

Timurik Patron 14.05.2020 в 11:25
    private function insert(array $mappedProperties): void
    {
        ...
        $this->refrech();
    }

    public function refrech(): void
    {
        $objFromDb = static::getById($this->id);
        foreach ($objFromDb as $property => $value) {
            $this->$property = $value;
        }
    }
ivashkevich 14.05.2020 в 15:04

refrech - опечатка в слове

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

Timurik Patron 15.05.2020 в 15:54

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

e.p.afonchenko@gmail.com 14.05.2020 в 18:34

Пробегаем по свойствам объекта, смотрим какие из них не установлены, делаем заброс в бд по нужному свойству и, если получаем ответ, записываем в свойство объекта полученное значение.

private function insert(array $mappedProperties)
{
    $columns = [];
    $params = [];
    $params2values = [];
    $index = 1;
    $filteredProperties = array_filter($mappedProperties);
    foreach ($filteredProperties as $column => $value) {
        $param = ':param' . $index;
        $columns[] = $column;
        $params[] = $param;
        $params2values[$param] = $value;
        $index++;
    }
    $sql = 'INSERT into ' . static::getTableName() . ' (' . implode(',', $columns) . ') VALUES(' . implode(',', $params) . ');';
    $db = Db::getInstance();
    $db->query($sql, $params2values, static::class);
    $this->id = $db->getLastInsertId();

    foreach ($this as $property=>$value){
        if($value === null){
            $underscoredProperty = $this->camelCaseToUnderscore($property);
            $sql = 'SELECT `'.$underscoredProperty.'` FROM `'.static::getTableName().'` WHERE id = :id';
            $queryResult = $db->query($sql, [':id' => $this->id]);
            if($queryResult != null) {
                $this->$property = $queryResult[0]->$underscoredProperty;
            }
        }
    }
}
ivashkevich 15.05.2020 в 18:22

Решение рабочее, но слишком сложное. Очень много лишних запросов. Можно обойтись одним SELECT-ом. Если не получится, посмотрите другие решения в комментариях.

e.p.afonchenko@gmail.com 16.05.2020 в 00:02

Переделал в один запрос. Так лучше?

    private function insert(array $mappedProperties)
    {
        $columns = [];
        $params = [];
        $params2values = [];
        $index = 1;
        $filteredProperties = array_filter($mappedProperties);
        foreach ($filteredProperties as $column => $value) {
            $param = ':param' . $index;
            $columns[] = $column;
            $params[] = $param;
            $params2values[$param] = $value;
            $index++;
        }
        $sql = 'INSERT into ' . static::getTableName() . ' (' . implode(',', $columns) . ') VALUES(' . implode(',', $params) . ');';
        $db = Db::getInstance();
        $db->query($sql, $params2values, static::class);
        $this->id = $db->getLastInsertId();

        $undefinedProperties = [];
        $underscoredUndefinedProperties = [];
        foreach ($this as $property=>$value){
            if($value === null){
                $undefinedProperties[] = $property;
                $underscoredUndefinedProperties[] = $this->camelCaseToUnderscore($property);
            }
        }

        $sql = 'SELECT '.implode(',', $underscoredUndefinedProperties).' FROM `'.static::getTableName().'` WHERE id = :id';
        $db = Db::getInstance();
        $sqlResult = $db->query($sql,[':id' => $this->id]);

        if($sqlResult !== null) {
            for ($i = 0; $i < count($undefinedProperties); $i++) {
                $undefinedProperty =  $undefinedProperties[$i];
                $underscoredUndefinedProperty = $underscoredUndefinedProperties[$i];
                $this->$undefinedProperty = $sqlResult[0]->$underscoredUndefinedProperty;
            }
        }
    }
ivashkevich 16.05.2020 в 12:18

Только непонятно, для чего это. У вас же уже есть метод для получения объекта из базы.

e.p.afonchenko@gmail.com 16.05.2020 в 18:13

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

$entity = self::getById($this->id);
foreach ($this as $property=>$value){
    if($value === null){
        $this->$property = $entity->$property;
    }
}

И можно ли вместо

if($value === null){
    $this->$property = $entity->$property;
}

писать

 $value ?? $this->$property = $entity->$property;
ivashkevich 17.05.2020 в 14:53

Некорректное использование null-coalesce оператора. Он используется для возврата значений в правой части в левую часть выражения. У вас же он применяется для побочного вызова кода в случае выполнения или невыполнения условия. Используйте в таких случаях if-else.

HardBass Patron 27.06.2020 в 15:49

ActiveRecordEntity.php

...
        $this->refresh();;
    }

    private function refresh() : void
    {
        $objDb = static::getById($this->id);
        foreach ($objDb as $property => $value) {
            $this->$property = $value;
        }
    }
...
ivashkevich 27.06.2020 в 17:05

Отлично!

zeexo 05.08.2020 в 21:50

Как можно решить проблему, если я через сеттер в модели задаю значение = 0
У меня тогда ActiveRecordEntity просто не учитывает это в insert запросе(просто пропускает поле, значение которого = 0).
Хотя если значение 1, 2, 3 или любое текстовое - то всё ок.

ivashkevich 07.08.2020 в 17:55

Видимо значение отфильтровывается в array_filter. Вы дебажить пробовали?

zeexo 07.08.2020 в 19:11

Проблема возникает в ActiveRecordEntity, в строке:
$filteredProperties = array_filter($mappedProperties);
у метода insert, то есть функция array_filter удаляет элементы входящего массива, если у него значение = 0.
Проблему решил так(при этом null, false, пустая строка будут так же удаляться из массива):

$filteredProperties = array_filter($mappedProperties, 'strlen');

Возможно кому-то пригодится.

ivashkevich 11.08.2020 в 13:18

Я ожидал что вы всё-таки свой коллбэк напишите и будете сравнивать с null. Потому что в вашем варианте будут и пустые строки отфильтровываться.

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