Связь MANY-MANY по комплексному PRIMARY KEY

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
Аватара пользователя
porcelanosa
Сообщения: 547
Зарегистрирован: 2010.03.16, 04:31
Откуда: Москва

Связь MANY-MANY по комплексному PRIMARY KEY

Сообщение porcelanosa » 2018.03.01, 02:57

Задача - есть сущность "город" и сущность "категория". Для каждого "города" должно бы описание каждой категории.
Изображение Как-то так.
Но совсем не понимаю, как это в админке оформить.
Пробовала разные варианты, но даже не хочу приводить их.
Запуталась совсем.
Подтолкните - куда копать?
UPD:
В модели:

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

/**
         * @return \yii\db\ActiveQuery
         */
        public function getCatdescr($cat_slug)
        {
            $cat_id = Cats::find()->where(['slug' => $cat_slug])->one()->id;
            
            return $this->hasOne(CatsTownDescr::className(), ['town_id' => 'id'])->viaTable('cats_town_descr', ['cat_id' => $cat_id]);
        }
Написала вот такое. Но как это в ActiveForm можно засунуть?
Попробовала вот так во View

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

                <!-- Nav tabs -->
                <ul class="nav nav-tabs">
                    <?foreach ($cats as $cat):?>
                        <li><a href="#<?=$cat->slug?>" data-toggle="tab"><?=$cat->name?></a></li>
                    <?endforeach;?>
                </ul>

                <!-- Tab panes -->
                <div class="tab-content">
                    <?foreach ($cats as $cat):?>
                        <div class="tab-pane active" id="<?=$cat->slug?>">
                            <?=$form->field($model, 'catdescr('.$cat->slug.')')->textarea(['rows'=>3])?>
                        </div>
                    <?endforeach;?>
                </div>
Но естественно не работает
mcintosh-club.ru - первый мой сайт с использование Yii //
Акустика Sonus Faber Hi-End класса//
Необрезная доска и другие пиломатериалы

Аватара пользователя
proctoleha
Сообщения: 276
Зарегистрирован: 2016.07.10, 19:00

Re: Связь MANY-MANY по комплексному PRIMARY KEY

Сообщение proctoleha » 2018.03.01, 08:30

Если я правильно понял, то формальная задача выглядит так:

1. Есть список категорий, например, С, С1, С2 одним из полей является описание категории
2. Есть список городов, например, S, S1, S2
3. При выводе городов должны появляться описания всех категорий из списка категорий

1. Поле descr должно принадлежать таблице cats, но никак не сводной таблице. В сводной таблице будут только два поля cat_id и town_id. И связи hasMany cats и hasMany town

2. В таблицах cats и town тоже связи hasMany cat_id и hasMany town_id

3. В модели Town объявить публичное поле categoryDescription

А вот дальше не представляю что должно быть.

Вы пишете про админку. При редактировании сущности Город вы хотите заодно редактировать описания категорий? Если это так, то это неверно. Описания категорий должны относится к категориям и редактироваться в сущности Категория. Или вы что-то др. хотите?
Вот за что я не люблю линукс, так это за свои кривые, временами, руки

Аватара пользователя
porcelanosa
Сообщения: 547
Зарегистрирован: 2010.03.16, 04:31
Откуда: Москва

Re: Связь MANY-MANY по комплексному PRIMARY KEY

Сообщение porcelanosa » 2018.03.01, 12:27

proctoleha писал(а):
2018.03.01, 08:30
Если я правильно понял, то формальная задача выглядит так:

1. Есть список категорий, например, С, С1, С2 одним из полей является описание категории
2. Есть список городов, например, S, S1, S2
3. При выводе городов должны появляться описания всех категорий из списка категорий

1. Поле descr должно принадлежать таблице cats, но никак не сводной таблице. В сводной таблице будут только два поля cat_id и town_id. И связи hasMany cats и hasMany town

2. В таблицах cats и town тоже связи hasMany cat_id и hasMany town_id

3. В модели Town объявить публичное поле categoryDescription

А вот дальше не представляю что должно быть.

Вы пишете про админку. При редактировании сущности Город вы хотите заодно редактировать описания категорий? Если это так, то это неверно. Описания категорий должны относится к категориям и редактироваться в сущности Категория. Или вы что-то др. хотите?
Не совсем так.
Еще есть места. Так что категории связаны с местами. И города связаны с местами. Т.е. есть категории Музеи, Церкви и т.д. Есть место Краеведческий музей в город Тамбове. И есть страница Музеи в городе Тамбове. Там простым запросом по cat_id и town_id выводятся "места"
И вот на этой странице должно быть описание категории Музеи в городе Тамбове. Типа - "В Тамбове полно музеев - они клёвые"
mcintosh-club.ru - первый мой сайт с использование Yii //
Акустика Sonus Faber Hi-End класса//
Необрезная доска и другие пиломатериалы

Аватара пользователя
porcelanosa
Сообщения: 547
Зарегистрирован: 2010.03.16, 04:31
Откуда: Москва

Re: Связь MANY-MANY по комплексному PRIMARY KEY

Сообщение porcelanosa » 2018.03.01, 14:34

Изображение Вот так.
Если полностью.
Для каждой пары категория-город - должно быть описание.
По-моему структура правильная.
Сделала вот так:
_form.php

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

<? if (!$model->isNewRecord) : ?>
            <div class="row">
                <div class="col-md-12">
                    <!-- Nav tabs -->
                    <ul class="nav nav-tabs">
                        <? $tab_count = 0 ?>
                        <? foreach ($cats as $cat): ?>
                            <? (++$tab_count == 1) ? $tab_active_class = 'active' : $tab_active_class = '' ?>
                            <li class="<?=$tab_active_class?>"><a href="#<?=$cat->slug?>" data-toggle="tab"><?=$cat->name?></a></li>
                        <? endforeach; ?>
                    </ul>

                    <!-- Tab panes -->
                    <div class="tab-content">
                        <? $tab_count = 0 ?>
                        <? foreach ($cats as $cat): ?>
                            <? $value = \common\models\CatsTownDescr::find()->where(['cat_id' => $cat->id, 'town_id' => $model->id])->one()->descr ?>
                            <? (++$tab_count == 1) ? $tab_active_class = 'active' : $tab_active_class = '' ?>
                            <div class="tab-pane <?=$tab_active_class?>" id="<?=$cat->slug?>">
                                <? //=$form->field($model, 'catdescr('.$cat->slug.')')->textarea(['rows'=>3])?>
                                    <div class="form-group">
                                        <label for='catdescr_<?=$cat->slug?>' class="control-label">Описание для категории <?=$cat->plural_name?></label>
                                <?=Html::textarea('catdescr_' . $cat->slug, $value, ['rows' => 3, 'cols'=>20, 'style'=>'', 'class'=>'form-control'])?>
                                    </div>
                            </div>
                        <? endforeach; ?>
                    </div>
                </div>
            </div>
        <? endif; ?>
TownsController.php

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

public function actionUpdate($id)
        {
            $town = $this->findModel($id);
            $cat_descr = [];
            $cats = Cats::find()->all();
            if ($town->load(Yii::$app->request->post()) && $town->save()) {
                $cat_descr[]                 = ($_POST['catdescr_food']);
                foreach ($cats as $cat) {
                    ${'cat_descr_' . $cat->slug} = Yii::$app->request->post('catdescr_' . $cat->slug);
                    
                    if (${'cat_descr_' . $cat->slug} != '') {
                        $currentCatDescr = CatsTownDescr::find()->where(['cat_id' => $cat->id, 'town_id' => $town->id])->one();
                        if ($currentCatDescr) {
                            $currentCatDescr->descr = ${'cat_descr_' . $cat->slug};
                            $currentCatDescr->save();
                        } else {
                            $catDescr          = new CatsTownDescr();
                            $catDescr->cat_id  = $cat->id;
                            $catDescr->town_id = $town->id;
                            $catDescr->descr   = ${'cat_descr_' . $cat->slug};
                            $catDescr->save();
                        }
                    }
                }
                //var_dump($cat_descr);
                
                return $this->redirect(['update', 'id' => $town->id]);
            } else {
                return $this->render('update', [
                    'model' => $town,
                    'cats'  => $cats
                ]);
            }
        }
Получилось криво, Но работает.
mcintosh-club.ru - первый мой сайт с использование Yii //
Акустика Sonus Faber Hi-End класса//
Необрезная доска и другие пиломатериалы

andku83
Сообщения: 988
Зарегистрирован: 2016.07.01, 10:24
Откуда: Харьков

Re: Связь MANY-MANY по комплексному PRIMARY KEY

Сообщение andku83 » 2018.03.01, 14:38

самый простой способ реализовать редактирование этих связей это сделать отдельный контроллер для редактирования этой модели с такой формой:

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

<?= $form->field($model, 'cat_id')->dropDownList(Category::getList()) ?>

<?= $form->field($model, 'town_id')->dropDownList(Town::getList()) ?>

<?= $form->field($model, 'descr')->textarea() ?>
и добавить в модель связи валидацию на наличие такой комбинации категория<->город

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

andku83
Сообщения: 988
Зарегистрирован: 2016.07.01, 10:24
Откуда: Харьков

Re: Связь MANY-MANY по комплексному PRIMARY KEY

Сообщение andku83 » 2018.03.01, 14:46

porcelanosa писал(а):
2018.03.01, 14:34
Получилось криво, Но работает.
для начала я бы не рекомендовал использовать разные конструкции

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

cat_descr_
catdescr_
потом сами запутаетесь.

сразу же минус вашего подхода вы не можете удалить уже существующую связь
и при добавлении новых данных вы каждый раз перезаписываете старые данные используя на каждой операции 2 SQL запроса (чтение и запись)


Аватара пользователя
porcelanosa
Сообщения: 547
Зарегистрирован: 2010.03.16, 04:31
Откуда: Москва

Re: Связь MANY-MANY по комплексному PRIMARY KEY

Сообщение porcelanosa » 2018.03.01, 16:34

shnir писал(а):
2018.03.01, 14:46
porcelanosa писал(а):
2018.03.01, 14:34
Получилось криво, Но работает.
для начала я бы не рекомендовал использовать разные конструкции

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

cat_descr_
catdescr_
потом сами запутаетесь.

сразу же минус вашего подхода вы не можете удалить уже существующую связь
и при добавлении новых данных вы каждый раз перезаписываете старые данные используя на каждой операции 2 SQL запроса (чтение и запись)
По первому согласно - просто набросала пример.
По второму - ну это админка - не так это и критично. При обычной связи MANY-MANY тоже по-моему 2 запроса.
Так что пойдет.
mcintosh-club.ru - первый мой сайт с использование Yii //
Акустика Sonus Faber Hi-End класса//
Необрезная доска и другие пиломатериалы

Аватара пользователя
porcelanosa
Сообщения: 547
Зарегистрирован: 2010.03.16, 04:31
Откуда: Москва

Re: Связь MANY-MANY по комплексному PRIMARY KEY

Сообщение porcelanosa » 2018.03.01, 16:42

shnir писал(а):
2018.03.01, 14:38
самый простой способ реализовать редактирование этих связей это сделать отдельный контроллер для редактирования этой модели с такой формой:

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

<?= $form->field($model, 'cat_id')->dropDownList(Category::getList()) ?>

<?= $form->field($model, 'town_id')->dropDownList(Town::getList()) ?>

<?= $form->field($model, 'descr')->textarea() ?>
и добавить в модель связи валидацию на наличие такой комбинации категория<->город

более сложная реализация возможна на отдельной вкладке редактирования каждой из моделей где будет выпадающий список связуемых моделей с исключением уже подключенных связей и при выборе из этого списка создавать подобную строку с инпутом для ввода описания
Ну отдельная страница - это не вариант. Неудобно даже мне будет, не то что пользователям.
По ссылке просто множественное добавление модели. Там речь не о связях.
Кстати, не поняла как они сохраняются. Вроде бы класса для сохранения нет.
mcintosh-club.ru - первый мой сайт с использование Yii //
Акустика Sonus Faber Hi-End класса//
Необрезная доска и другие пиломатериалы

Аватара пользователя
porcelanosa
Сообщения: 547
Зарегистрирован: 2010.03.16, 04:31
Откуда: Москва

Re: Связь MANY-MANY по комплексному PRIMARY KEY

Сообщение porcelanosa » 2018.03.01, 16:45

Ну хотелось бы хотя бы фрагменты кода. По картинке не совсем ясно, как это работает.
Вообще проблема именно в сохранении.
Допустим если можно задать стандартную связь многое ко многим - полно расширений для такого сохранения. Можно оттуда что-то почерпнуть.
Там все просто.
mcintosh-club.ru - первый мой сайт с использование Yii //
Акустика Sonus Faber Hi-End класса//
Необрезная доска и другие пиломатериалы

andku83
Сообщения: 988
Зарегистрирован: 2016.07.01, 10:24
Откуда: Харьков

Re: Связь MANY-MANY по комплексному PRIMARY KEY

Сообщение andku83 » 2018.03.01, 17:33

porcelanosa писал(а):
2018.03.01, 16:34
По второму - ну это админка - не так это и критично. При обычной связи MANY-MANY тоже по-моему 2 запроса.
Так что пойдет.
при получении через with() 2 запроса на получение ВСЕХ связанных данных по второй связи, в вашем же случае для получения ОДНОЙ записи используется один запрос и для сохранения этой записи другой (ДЛЯ ВСЕХ ИМЕЮЩИХСЯ связей ) = 2*n запросов, а невозможность удаления вы просто проигнорировали
porcelanosa писал(а):
2018.03.01, 16:42
Ну отдельная страница - это не вариант. Неудобно даже мне будет, не то что пользователям.
По ссылке просто множественное добавление модели. Там речь не о связях.
отдельная страница иногда может упростить работу и понимание - все зависит от конкретики
по ссылке просто создание моделей - но приводилось как идея для динамического создания связей
porcelanosa писал(а):
2018.03.01, 16:45
Ну хотелось бы хотя бы фрагменты кода. По картинке не совсем ясно, как это работает.
Вообще проблема именно в сохранении.
это можно улучшить, написано было уже давно, а как известно - "Работает - не трогай!"

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

    public function afterSave($insert, $changedAttributes)
    {
        parent::afterSave($insert, $changedAttributes);
        if ($this->scenario == self::SCENARIO_SAVE_WITH_RELATION ){
            $this->updateProperties();
        }
    }
    
    protected function updateProperties()
    {
        $current = $this->getProductProperties()->asArray()->indexBy('property_id')->all();
        $post = Yii::$app->request->post();

        $new = (!empty($post['Product']['properties']) && is_array($post['Product']['properties']))
            ? $post['Product']['properties'] : [];

        // insert rows
        foreach (array_filter(array_diff_key($new, $current)) as $key => $item) {
            $prod_prop = new ProductProperty(
                array_merge(
                    ['product_id'  => $this->id,'property_id' => $key],
                    $item
                )
            );
            $prod_prop->save();
        }

        // delete rows
        if ($deleteProperties = array_filter(array_keys(array_diff_key($current, $new)))) {
            ProductProperty::deleteAll([
                'product_id' => $this->id,
                'property_id' => $deleteProperties
            ]);
        }

        // update rows
        foreach ($this->prop_diff($new, $current) as $key => $update){
            ProductProperty::updateAll($update, ['product_id' => $this->id, 'property_id' => $key]);
        }
    }
    
    protected function prop_diff(array $first, array $second)
    {
        $result = [];
        foreach ($first as $key => $item) {
            if(isset($second[$key])
                && ($item['value_id'] != $second[$key]['value_id'] || $item['in_list'] != $second[$key]['in_list'])){
                    $result[$key] = $item;
            }
        }
        return $result;
    }
а во вью используется движет основанный на таком и значительно доработанном

Ответить