Yii2 ActiveRecord нужна помощь по связям

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
Аватара пользователя
alexchep
Сообщения: 8
Зарегистрирован: 2019.01.14, 17:04

Yii2 ActiveRecord нужна помощь по связям

Сообщение alexchep »

Имеются таблицы, в которых в БД нет связей и проставить их там не получится.
Вопрос: могу ли я в коде класса от ActiveRecord как-то "силой" прописать их? Чтобы можно было добавить и нормально использовать методы hasOne/hasMany.
Спасибо заранее за ответы.
yiiliveext
Сообщения: 910
Зарегистрирован: 2019.08.13, 01:49

Re: Yii2 ActiveRecord нужна помощь по связям

Сообщение yiiliveext »

В основной таблице должен быть первичный ключ. А так, можете прописать связь вручную.
Аватара пользователя
alexchep
Сообщения: 8
Зарегистрирован: 2019.01.14, 17:04

Re: Yii2 ActiveRecord нужна помощь по связям

Сообщение alexchep »

yiiliveext писал(а): 2020.03.06, 11:14 В основной таблице должен быть первичный ключ. А так, можете прописать связь вручную.
А где именно их прописывать? Возможно есть пример как это примерно должно выглядеть?
Если имеется ввиду миграция, то не вариант. Нужно именно в рантайме проставить их.

UPD: в основной таблице нет PK. Проектировал БД не я. По-этому и сложности сейчас. Переписываю проект, но в БД ничего менять нельхя, так как первое время будет ходить старый туда.
yiiliveext
Сообщения: 910
Зарегистрирован: 2019.08.13, 01:49

Re: Yii2 ActiveRecord нужна помощь по связям

Сообщение yiiliveext »

alexchep писал(а): 2020.03.06, 11:26
yiiliveext писал(а): 2020.03.06, 11:14 В основной таблице должен быть первичный ключ. А так, можете прописать связь вручную.
А где именно их прописывать? Возможно есть пример как это примерно должно выглядеть?
Если имеется ввиду миграция, то не вариант. Нужно именно в рантайме проставить их.
Что значит в рантайме? Связи прописываются в коде класса ActiveRecord (вашей модели)

Код: Выделить всё

// model Student
public function getRatings()
{
    return $this->hasMany(Rating::className(), ['student_id' => 'id']);
}

Код: Выделить всё

//model Rating
public function getStudent()
{
    return $this->hasOne(Student::className(), ['id' => 'student_id']);
}
Аватара пользователя
alexchep
Сообщения: 8
Зарегистрирован: 2019.01.14, 17:04

Re: Yii2 ActiveRecord нужна помощь по связям

Сообщение alexchep »

Что значит в рантайме? Связи прописываются в коде класса ActiveRecord (вашей модели)

Код: Выделить всё

// model Student
public function getRatings()
{
    return $this->hasMany(Rating::className(), ['student_id' => 'id']);
}

Код: Выделить всё

//model Rating
public function getStudent()
{
    return $this->hasOne(Student::className(), ['id' => 'student_id']);
}
Это понятно. Но если нет PK. И нет связи таблиц явных в БД. То эти методы не сработают. Будет ругаться, что не может найти связь. Вот я хочу узнать, можно ли каким-то способом игнорить это и говорить с кода, что вот у тебя будет такой PK, и свяжись с той таблицей. Но без миграции. Чтобы ничего не менялось в БД. То есть это будет происходить в рантайме, когда приложение запуститься. Есть варианты ?
yiiliveext
Сообщения: 910
Зарегистрирован: 2019.08.13, 01:49

Re: Yii2 ActiveRecord нужна помощь по связям

Сообщение yiiliveext »

Без PK в основной таблице из коробки не выйдет сделать. Хотя, надо глянуть, я точно не помню как там сделано внутри классов AR.

Посмотрел, там это публичный метод, PK можно прямо в модели задать.

Код: Выделить всё

//model Student
public static function primaryKey()
{
    return ['id'];
}
yiiliveext
Сообщения: 910
Зарегистрирован: 2019.08.13, 01:49

Re: Yii2 ActiveRecord нужна помощь по связям

Сообщение yiiliveext »

alexchep писал(а): 2020.03.06, 11:38 То есть это будет происходить в рантайме, когда приложение запуститься. Есть варианты ?
В рантайме в этом нет смысла, в модель первичный ключ добавить - в посте выше написал.
skynin
Сообщения: 400
Зарегистрирован: 2017.12.12, 10:09

Re: Yii2 ActiveRecord нужна помощь по связям

Сообщение skynin »

alexchep писал(а): 2020.03.06, 10:59 Имеются таблицы, в которых в БД нет связей и проставить их там не получится.
Вопрос: могу ли я в коде класса от ActiveRecord как-то "силой" прописать их? Чтобы можно было добавить и нормально использовать методы hasOne/hasMany.
При формировании запроса создаваемого с помощью hasOne/hasMany проверяется наличие первичного ключа.
Если его нет, то все, hasOne/hasMany не будет работать

Для того чтобы добиться такого же поведения и использования, я сделал простенький трейт, который и подмешиваю

Код: Выделить всё

/**
 * Трейт позволяет создавать произвольные проперти заполнямые вызовом метода класса
 * Стандартный механизм ограничен созданием отношений с помощью hasOne hasMany
 *
 * Класс обязан объявить массив
 * protected static $dynamicProps = [...]
 * ключ - имя проперти
 * значение - имя метода. метод может вернуть ActiveQuery, тогда будет вызван ->all()
 *
 * @author skynin
 */
trait DynamicPropsTrait
{
    private $_dynamicProps = [];

    public function __get($name)
    {
        if (key_exists($name, self::$dynamicProps)) {
            if (empty($this->_dynamicProps[$name])) {
                $queryMethodName = self::$dynamicProps[$name];

                $query = $this->$queryMethodName();

                $this->_dynamicProps[$name] = $query instanceof \yii\db\ActiveQuery ? ($query->all()) : $query; // если нужен one() то расширить конфигурирование $dynamicProps
            }

            return $this->_dynamicProps[$name];
        }

        return parent::__get($name);
    }

    public function __set($name, $value)
    {
        if (key_exists($name, self::$dynamicProps)) {
            $this->_dynamicProps[$name] = $value;
            return;
        }

        parent::__set($name, $value);
    }

    public function __unset($name)
    {
        if (key_exists($name, self::$dynamicProps)) {
            unset($this->_dynamicProps[$name]);
            return;
        }

        parent::__unset($name);
    }

    public function __isset($name): bool
    {
        return key_exists($name, self::$dynamicProps) || parent::__isset($name);
    }
}

Пример использования

Код: Выделить всё

/*
 * @property SmanAR[] $smen
 */
class SportMatchAR ....
{
    use DynamicPropsTrait;

    protected static $dynamicProps = [
        'smen' => 'getSmen',
    ];

    public function getSmen()
    {
        return SmanAR::find()->andWhere(/*что нужно*/);
    }
}

// и далее как обычно
$matchSmen= $spotMatch->smen;
Наверное можно и еще элегантней, но как все временное - в проектах такое решение оказалось вечным :)
Не желайте странного, и не будет у вас головной боли чтобы достичь этого странного.
Тем более что окажется что оно вам и не нужно было, странное это.
yiiliveext
Сообщения: 910
Зарегистрирован: 2019.08.13, 01:49

Re: Yii2 ActiveRecord нужна помощь по связям

Сообщение yiiliveext »

skynin писал(а): 2020.03.06, 11:59
При формировании запроса создаваемого с помощью hasOne/hasMany проверяется наличие первичного ключа.
Если его нет, то все, hasOne/hasMany не будет работать
Ручное задание первичного ключа решает эту проблему.
Для того чтобы добиться такого же поведения и использования, я сделал простенький трейт, который и подмешиваю
Оно не будет работать с тем же with и joinWith.
skynin
Сообщения: 400
Зарегистрирован: 2017.12.12, 10:09

Re: Yii2 ActiveRecord нужна помощь по связям

Сообщение skynin »

yiiliveext писал(а): 2020.03.06, 12:17 Ручное задание первичного ключа решает эту проблему.
Связи бывают гораздо хитрее, например с выборками по актуальности итогов
И то что у записи есть первичный ключ - никак не помогает.

То есть связи из бизнес-логики не всегда можно реализовать с помощью PK.
Хотя на уровне реляционных отношений они очевидны и естественны. И сами запросы - эффективны в выполнении.
Оно не будет работать с тем же with и joinWith.
совершенно верно - именно с этими исключительными полями в ActiveRecord - не будет. Как я понял по коду, именно ради этого так жестко и работают hasOne, hasMany - только с PK

Но обычно то - и не надо.
Решение было как временное, но стало вечным потому что вот как-то не понадобилось еще использовать такое поле в with и joinWith

Но, если вот никак без них, для таких полей в AR - тогда все просто - есть исходники Yii2, наследуйте цепочку объектов ORMа, расширяйте - дописывайте ядро короче :)
Не желайте странного, и не будет у вас головной боли чтобы достичь этого странного.
Тем более что окажется что оно вам и не нужно было, странное это.
yiiliveext
Сообщения: 910
Зарегистрирован: 2019.08.13, 01:49

Re: Yii2 ActiveRecord нужна помощь по связям

Сообщение yiiliveext »

skynin писал(а): 2020.03.06, 12:31 Связи бывают гораздо хитрее, например с выборками по актуальности итогов
И то что у записи есть первичный ключ - никак не помогает.

То есть связи из бизнес-логики не всегда можно реализовать с помощью PK.
Хотя на уровне реляционных отношений они очевидны и естественны. И сами запросы - эффективны в выполнении.
Я не думаю, что у ТС там какие-то мега-хитрые связи. Он просто спросил как ему сделать обычные hasOne()/hasMany() если у него нет ключей в таблицах. В этом случае решение с заданием первичного ключа вручную является самым простым и валидным.
skynin
Сообщения: 400
Зарегистрирован: 2017.12.12, 10:09

Re: Yii2 ActiveRecord нужна помощь по связям

Сообщение skynin »

yiiliveext писал(а): 2020.03.06, 12:46 Он просто спросил как ему сделать обычные hasOne()/hasMany() если у него нет ключей в таблицах. В этом случае решение с заданием первичного ключа вручную является самым простым и валидным.
Я поделился тем чем пользуюсь несколько лет. Проблем не было.
Топикастер пусть и решает, какой способ ему самый простой и валидный.
Не желайте странного, и не будет у вас головной боли чтобы достичь этого странного.
Тем более что окажется что оно вам и не нужно было, странное это.
Аватара пользователя
alexchep
Сообщения: 8
Зарегистрирован: 2019.01.14, 17:04

Re: Yii2 ActiveRecord нужна помощь по связям

Сообщение alexchep »

Спасибо за ответы. Буду пробовать.
Ответить