Работа со связями моделей в Yii2 на примере каталога

Сначала хотел провести вебинар по связям ActiveRecord-моделей в Yii2 на основе урока о теории баз данных, осветив несколько тем из документации. Но получилась бы куча «воды» без практики. Так что решил показать на примере разработки каталога для интернет-магазина из реальной жизни:

Открыть на YouTube | Исходники примера

Приглашаю на следующие видеоуроки. Анонс и ссылку на эфир, как обычно, пришлю в отдельной рассылке по вебинарам:

И задавайте вопросы в комментариях. Заранее спасибо и до встречи в эфире!

Комментарии

 

Макс

Димон, готовь курс по Yii2, тебе удается всё грамотно и понятно объяснять, а те курсы которые существуют явно нет то, миру необходим курс от профи =)

Ответить

 

Алекс

Да, обсуждалось это на вебинаре. Дмитрий высказал мысль, что прологом к такому курсу будет ликбез по ООП. Считаю это неправильным. Такой подход превратит (ну может не превратит, но приблизит) к уже существующим бездарным. Не нужно тратить на это время. Тем, кому нужен Yii уже следовало бы изучить ООП. Если не изучили - материала как блох на собаке.

Ответить

 

Дмитрий Елисеев

> Тем, кому нужен Yii уже следовало бы изучить ООП.

А тем, кто изучил ООП, уже не пригодится курс ни по какому фреймворку.

Я под ООП имею в виду всю экосистему принципов, паттернов, антипаттернов, метрик технического и визуального качества кода. Применения принципов и паттернов по назначению именно как средств уменьшения сложности. То есть всех составляющих осознанного объектно-ориентированного мышления.

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

Ответить

 

Алекс

И все же позволю себе не согласиться.

Что значит "изучить"? Год назад я начинал с c# (в отличии от PHP, си шарп чисто ООП язык). Кроме того, прочел пару книг по паттернам. В частности "Паттерны проектирования" Фрименов, и "PHP объекты, шаблоны и методики". И я как раз в той стадии, когда "уже Не, но еще и НЕ". Так вот, теория это одно, а практический разбор фреймворка, да еще и от такого хорошего специалиста и учителя - это совсем другое. Так что с фразой "кто изучил ООП, уже не пригодится курс ни по какому фреймворку" не согласен категорически.

Ответить

 

makeloo – langworker.com

Я бы с удовольствием посмотрел бы хороший курс видео по Yii2 от Дмитрия. Если платно то подумал бы о том чтобы прикупить.

Ответить

 

HiStO rIaN

Когда вы уже смиритесь, что фреемворк это отдельный язык, ни на что не похожий. Если человек знает основной язык программирования, это не значит, что он легко освоит фреемворки! Тоже самое можно сказать, но ты же знаешь какой нибудь Perl зачем тебе уроки по php? Они никому не нужны.

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

Столько уроков просмотрел, только единицы немного объясняют суть всего, там даже и 1% нету из всех "учителей".

Вы и всякие русаковы, которые создают уроки гавнокодинга, и не умеют вкладывать знания в голову, хотя обязаны. Это факт. Вместо того, чтобы кидаться на людей, научитесь учить, доставлять информацию для людей, и нормально разжовывать

Ответить

 

Дмитрий Елисеев

> Когда вы уже смиритесь, что фреемворк это отдельный язык, ни на что не похожий.

Фреймворки - это более профессиональный уровень, на который люди осознанно переходят со своих начальных самописов или с готовых CMS, когда готовый код их перестаёт устраивать.

Профессиональные фреймворки пишутся по продвинутым архитектурным принципам. Там уже нужно уметь профессионально программировать и разбираться в архитектуре, а не просто прочитать "PHP для чайников". Именно поэтому лезть "с полного нуля" сразу в меганавороченные фреймворки - это всегда туго.

А потом уже по принципам работы почти все фреймворки друг на друга похожи. Во всех есть абстракции над HTTP, маршрутизаторы, контроллеры, шаблоны и что-то для баз данных.

> Если человек знает основной язык программирования, это не значит, что он легко освоит фреемворки!

Об этом и говорю. Если человек уже "созрел" и знает не только язык, но и ООП, SQL, HTTP, общеупотребительные паттерны, практики и архитектурные принципы, то спокойно освоит фреймворки. А если не знает и знать не хочет, то бегает по всем статьям и форумам и кричит, что его никто не учит.

> не умеют вкладывать знания в голову, хотя обязаны

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

Ответить

 

HiStO rIaN

Вот опять только отмазки, и ничего по делу. Фреемворк это язык. В него нельзя зайти зная тот же ооп. Я его достаточно хорошо знаю. Этот язык фреемворка надо учить, он не похож ни на один язык, и как раз вы обязаны учить, раз уж сами вызвались это делать. А вы только огрызаетесь, вместо того, чтобы понять факт!

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

То что вам жопу все лижут, это не говорит о том, что вы хорошо разбираетесь. Если вы не видите фактов, это тоже не мои проблемы.

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

Вы бы хоть перечитали что пишите. Ни имет файлов, ни имен папок, или это мы тоже должны ванговать и знать ?

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

После вашего "учения" только каша в голове! Даже бесполезная дока, и то больше информации дает, хотя что там черпать, когда ничего нету... Вот и задумайтесь

Ответить

 

Дмитрий Елисеев

> Фреемворк это язык. В него нельзя зайти зная тот же ооп. Я его достаточно хорошо знаю.

Значит обманываете себя и меня, что "достаточно хорошо знаете". Умение написать слово class - это ещё не "знание ООП".

> Этот язык фреемворка надо учить, он не похож ни на один язык.

Не путайте "язык" и "архитектуру". Web-фреймворки есть на PHP, Python, Ruby, Java, C# и остальных ОО языках. Все почти одинаково написаны по одним и тем же ООП-принципам и архитектурным паттернам. Сам язык здесь никакого значения не имеет.

> Даже бесполезная дока, и то больше информации дает, хотя что там черпать, когда ничего нету...

В этом и причина. Вы "осилили" только "бесполезную доку" по Yii и теперь ноете, что там ничего нет. Расширяйте кругозор. Просмотрите все доки по CodeIgniter, Yii, Phalcon, CakePHP, Laravel, Symfony, Zend, Slim, Zend Expressive... Тогда и придёт некое начально понимание. Разжовывать за Вас никто это не будет.

> И как раз вы обязаны учить, раз уж сами вызвались это делать.

Я не вызывался в статьях никого учить. Вам показалось.

Ответить

 

HiStO rIaN

Ахаха, какие нахер доки? Мне делать нехер что ли? Это ваша обязаность объяснять, вы сами сюда пришли и сказали я учитель, так учите! Я не ною, а говорю факт! С которым согласятся все адекватные и нормальные люди, которые только начинают учить фреемворки.

И еще раз повторю, я очень хорошо понимаю ооп и php. В Yii2 могу многое создать, кроме этой убогой регистрации/авторизации/аунтификации, с которой рукожопы создатели намудрили.

Ответить

 

Дмитрий Елисеев

> Это ваша обязаность объяснять, вы сами сюда пришли и сказали я учитель, так учите!

Вам показалось.

Ответить

 

HiStO rIaN

Я кажется сказал слушать факты а не кукарекать.... Мдаа, по хлебалу бы тебе навернуть пару раз, дрищ очкастый! Вообще уже берега попутал

Ответить

 

HiStO rIaN

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

Все ясно, гавнокодеры они такие... Идите гавнокодте на готовых бесполезных мусорках

Ответить

 

Олег

Ахахах, боевой нытик )))

Ответить

 

Корешь

Адрес пиши сынок, уже выезжаю объяснять!

Ответить

 

Иван

Ну ты и мудозвон :-) Лучше бы спасибо сказал за видео

Ответить

 

Petr – elisdn.ru

Ты как кошка которая не может достать масло и говорить оно вонючая!

Ответить

 

Сергей

Спасибо.
Как раз то что искал по поводу many-to-many связей. Делаю мультикатегории и не мог найти как это реорганизовывается на фреймворке, так что пример с тегами помог.

Добавлю что в миграциях, если выполняется более одного действия (создание таблицы и внешнего ключа или создание двух таблиц) в методе up() документация рекомендует использовать safeUp() для того чтобы если какое то действие зафейлится, то остальные действия откатятся. Может кому будет полезно.

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

Ответить

 

Дмитрий Елисеев

MySQL поддерживает откат транзакций только для данных, так как автоматически коммитит транзакции при изменении таблиц или полей. Так что для неё safeUp бесполезен.

Ответить

 

Дмитрий Елисеев

Групповых курсов пока не веду. Только репетиторствую, когда просят. Напишите на почту или в Контакты.

Ответить

 

Денис

Было сказано: "А тем, кто изучил ООП, уже не пригодится курс ни по какому фреймворку"
Дмитрий, не слушаейте никого, делайте курсы для новичков с ликбезом по ООП !

Ответить

 

Яромир

Сразу видно психологию школоты - никого не слушайте, а только меня слушайте. Книги читай, школяр!

Ответить

 

Сергей

Привет. Подскажи плз какой дистрибутив linux лучше всего подходит для веб разработки по твоим предпочтениям?

Ответить

 

Дмитрий Елисеев

Стандартный стек Apache/Nginx, PHP и MySQL везде есть (а где нет - собирается из исходников), так что без разницы.

Ответить

 

Сергей

Я имел ввиду операционную систему на ядре линукс какую выбрать лучше (убунту, минт ....), может я не так выразился, вроде согласно википедии дистрибутив линукса и ОС это одни и те же вещи.

Спасибо за твои старания, изучении yii2 с твоими уроками стало намного проще!

Ответить

 

Дмитрий Елисеев

Ну я и говорю, что на любом Линуксе PHP есть, так что без разницы.

Ответить

 

Денис

Ubuntu desktop operating system powers millions of PCs and laptops around the world :)

Ответить

 

Сергей

Хотелось бы иметь последнии версии php7 и mysql 5.7, но я так понял в линуксе есть сборки lamp и xampp и они еще на старых версиях

Ответить

 

Денис

Для обучения тебе php>5.6 и mariadb 5.5 хватит надолго. А потом сам разберешься. Недорогого хостинга с поддержкой php 7 нужно ещё поискать). Сборки типа xamp проще ставятся, но для обучения я бы посоветовал ставить все отдельно, чтобы отличать хотябы что такое php от sql ))))

Ответить

 

Сергей

Не понял с чего ты взял, что я не отличаю php от sql?

Ответить

 

Aleksandr – aleks-r.com

Сергей, я так же как и Денис решил, что вы только собираетесь заняться обучением. Про линукс. На сервера чаще ставят серверный вариант Debian / Ubuntu / CentOS. В принципе, на начальном этапе линукс Вам не понадобится. Для разработки чаще используют виртуалку внутри виндоуса. Серверная и десктопная версия линукса отличается только набором программ для визуальной части, для серверов его вырезают. Самый популярный дистр на базе Убунту (та на базе Дэбиан) - Минт. В него установлено всё, что нужно пользователю без технических знаний. Мне приглянулся Elementary OS. Это дело вкуса. Если очень грубо - то чаще разные варианты линукса различаются "обоями да вариантами менюшек". Я про клонов на базе убунты. Научитесь работать с Bash, это то, что линукс делает линуксом для простого обывателя и пользователя.

Ответить

 

Денис

Дмитрий, подскажите, пожалуйста для особо понятливых. Честно, я посмотрел все 4 часа видео)
У меня есть форма для заполнения данных из двух моделей. Вопрос по экшену create.
Как сохранить данные одной модели, взять id первой сохраненной модели, и сохранить этот id в соответствующем поле второй модели? Приведенный код ниже у меня не сохраняет данные ни одной модели(

public function actionCreate()
{
    $model = new Dailyinfo();
    $info = new Information();

    if ($model->load(Yii::$app->request->post()) && $info->load(Yii::$app->request->post()) && $info->save()&& $model->info_id=$info->id && $model->save()) {
        return $this->redirect(['view', 'id' => $model->id]);
    } else {
        return $this->render('create', [
            'model' => $model,
            'info' => $info,
        ]);
    }
}
Ответить

 

Дмитрий Елисеев

Сначала $model->save(), потом $info->save().

Ответить

 

Денис

Поменял весь порядок, добавил ещё несколько переменных для заполнения required полей, теперь $info сохраняется, а $model нет.
Мне кажется при сохранении $model не получает $info->id.

Ответить

 

Дмитрий Елисеев

Сделайте print_r($info) и print_r($model), чтобы не казалось.

Ответить

 

Денис

С вашей помощью, Дмитрий, заработало) Есть ли у вас замечания по моему коду, все таки стремлюсь постепенно к феншую, хотя понимаю, что ещё далеко) И думаю начинающим будет полезно.

public function actionCreate()
{
    $model = new Dailyinfo();
    $model->dept_id=1;
    $model->insert_date=date('Y-m-d');

    $info = new Information();
    $info->period_id=7;
    $info->forma_type=12;

    if (  
        $info->load(Yii::$app->request->post())
        && $info->save()
        && $model->load(Yii::$app->request->post())
    ) {
        $model->info_id=$info->id;
        $model->save();
        return $this->redirect(['view', 'id' => $model->id]);
    } else {
        return $this->render('create', [
            'model' => $model,
            'info' => $info,
        ]);
    }
}
Ответить

 

Дмитрий Елисеев

Я бы либо установку первоначальных значений перенёс в методы loadDefaultValues моделей. И вообще бы отдельную модель формы использовал.

Ответить

 

Евгений

Подскажите пожалуйста как изучить ООП? Что читать и что смотреть? На что делать упор?

Ответить

 

Дмитрий Елисеев

Про кое-что расказывал здесь. А так, думаю, в YouTube что-нибудь уже есть.

А так может интенсив проведу в этом месяце по материалу, который ученикам рассказываю. Подпишитесь пока на рассылку. Если надумаю, то в ней сообщу.

Ответить

 

Дмитрий Елисеев

Уговорили :) Запускаю интенсив.

Ответить

 

Кабаченко Виктор

Отличный вебинар.
Маленькое уточнение. Чтобы поместить в гриде количество записей в нескольких связанных таблицах, надо немного изменить ваше решение, указав DISTINCT в COUNT. Примерно так:

$query = Category::find()
  ->select(['{{%category}}.*', 
         'products_count' => new Expression('COUNT(DISTINCT {{%product}}.id)')]),
         'materials_count' => new Expression('COUNT(DISTINCT {{%material}}.id)')]),
  ->joinWith(['products', 'materials'], false)
  ->groupBy('{{%category}}.id')
Ответить

 

Andrewkha

В коммите added product tabular attributes editor ошибка.
При таком коде в контроллере

        foreach (array_diff_key($attributes, $values) as $attribute) {
            $values[] = new Value(['attribute_id' => $attribute->id]);
        }

и таком выводе в представлении

<?= $form->field($value, '[' . $value->productAttribute->id . ']value')


все работает, если только id-шники атрибутов в соотв таблице идут подряд. Если же что-то вроде 1, 2, 5 то все поломается, loadMultiple не подберет значения там, где не совпадут индексы.
В контроллере надо

$values[$attribute->id] = new Value(['attribute_id' => $attribute->id]);
Ответить

 

Дмитрий Елисеев

Спасибо! Исправил.

Ответить

 

Сергей

Дмитрий, а можешь объяснить как в Yii2 сделать древовидные комментарии, без использования nested sets и т д? И чтобы можно было самому регулировать уровень максимальной вложенности.

Ответить

 

Дмитрий Елисеев

У меня обычным parent_id. С выборкой всех одним запросом и с рекурсивным выводом. Максимальный сдвиг для вложенности делается простым CSS:

.comment {
    margin-left: 40px;
}
.comment > .comment > .comment .comment {
    margin-left: 0;
}
Ответить

 

Andrewkha

Дмитрий, добрый день!

Слегка запутался в join, with и joinWith.
Скажи, пожалуйста, я правильно понимаю, что joinWith с параметром false - это аналог простого Join? или есть какие-то особенности?

Ответить

 

Дмитрий Елисеев

Да, аналог. Но join'у нужно вручную имя таблицы и колонок указать, а joinWith работает автоматически по имени связи.

Ответить

 

Иван Васильев – www.nfsu-cup.com

Спасибо за вебинар. Смотрел в записи. Очень много полезного для себя почерпнул из этого видео.

Ближе к концу видео, в CatalogController.php вы поменяли имя параметра действия с $tag на $name.
Тогда нужно поменять параметр при формировании массива ссылок на тэги в представлении _item.php.

$tagLinks[] = Html::a(Html::encode($tag->name), ['tag', 'name' => $tag->name]);

Еще раз спасибо. Теперь собираюсь пересмотреть ваши более ранние вебминары.

Ответить

 

Andrewkha – sportforecast.net

Дмитрий, приветствую. Нужна Ваша помощь :) Вопрос по работе с моделью и ActiveDataProvider.

Итак, имеется Junction table простого вида
id_user
it_tournament
points

первые два - внешние ключи для соотв таблиц.
Задача - для массива, содержащего id_tournaments получить все записи с максимальными points.

Что я сделал.

        $query = [];
        foreach ($array as $one)
            $query[] = UsersTournaments::find()
                ->where(['id_tournament' => $one])
                ->orderBy(['points' => SORT_DESC])
                ->with('idTournament.country0')
                ->with('idUser')
                ->limit(1);

        $count = count($query);

        $toExecute = $query[0];
        for($i = 0; $i < $count - 1; $i++)
            $toExecute = $toExecute->union($query[$i + 1]);

        return $toExecute;

Если затем вызываю

$toExecute->all()

все работает хорошо, т.е. выбирает именно записи с максимальным значением points для каждого id_tournament.
Но если же я этот $toExecute скармливаю в качестве свойства query для ActiveDataProvider начинаются чудеса.
А именно, для первого id_tournament (и только для него) выдает все имеющиеся записи, не только с максимальным значением points. Т.е. limit(1) в запросе не срабатывает. Для всех последующих id_tournament все хорошо.
Через дебаггер посмотрел, какой выполняется запрос и очень удивился.
Для первого id_tournament выполняется limit(20), такое ощущение, что ActiveDataProvider подменяет мой limit(1) своим дефолтовым для пагинации. Что интересно, для всех последющих, которые цепляются через union все хорошо, там limit(1).

Есть идеи, как это можно пофиксить?

Ответить

 

Дмитрий Елисеев

Можно попробовать все UNION обернуть в подзапрос в скобках. А так точно не знаю.

Ответить

 

Денис

Подскажите, пожалуйста, как можно сделать так, чтобы при помещении в парку site\pages файла формата md (Markdown), он открывался как статическая страница?

Ответить

 

Дмитрий Елисеев

Открыть через file_get_contents и скормить парсеру:

$this->render('page', [
    'content' => yii\helpers\Markdown::process($md),
]);
Ответить

 

Sergey Aver

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

public function getIngredients()
{
    return $this->hasMany(Ingredients::className(), ['id' => 'ingredient_id']);
}

public function getReceptIngredient($id)
{
    return ReceptyIngredients::find()
        ->joinWith('ingredients')
        ->where(['recept_id'=>$id])
        ->all();	
    }
}

$items = ReceptyIngredients::getReceptIngredient(1);
foreach($items as $item) {
    print_r($item->ingredients);
}

выводит вот такой результат

Array
(
    [0] => frontend\models\Ingredients Object
        (
            ...
        )
)

А если обращаюсь к $item->ingredients->name

пишет что Trying to get property of non-object

Ответить

 

Дмитрий Елисеев

Потому что $item->ingredients - это тоже массив:

foreach ($item->ingredients as $ingredient) {
    echo $ingredient->name;
}
Ответить

 

Sergey Aver

А если я делаю связь многие ко многим, вообще выходит что выдает два массива, ингредиенты и мера веса, а мне надо чтобы был массив один где связаны две таблицы через промежуточную. Тогда надо другой запрос делать? В ручную через select??? Потому что результат моего запроса такой

SELECT `np_ingredients`.* FROM `np_ingredients` LEFT JOIN `np_recepty_ingredients` ON `np_ingredients`.`id` = `np_recepty_ingredients`.`ingredient_id` LEFT JOIN `np_measures` ON `np_recepty_ingredients`.`id_measure` = `np_measures`.`id` WHERE `recept_id`=485 

А хотелось бы такой

SELECT * FROM `np_ingredients` LEFT JOIN `np_recepty_ingredients` ON `np_ingredients`.`id` = `np_recepty_ingredients`.`ingredient_id` LEFT JOIN `np_measures` ON `np_recepty_ingredients`.`id_measure` = `np_measures`.`id` WHERE `recept_id`=485 
Ответить

 

Дмитрий Елисеев

C "SELECT * FROM ..." только вручную, так как ActiveRecord результаты JOIN-ов не парсит.

Ответить

 

Яромир

Дмитрий, во-первых, большое человеческое спасибо за Ваш труд и Вашу отзывчивость. Во-вторых, не поделитесь Вашими планами на будущие вебинары? Помнится на интенсиве по ООП много людей высказывало желание увидеть вебинары по устройству symfony. Собственно, это было бы прямым развитием темы ООП про правильное структуирование клиентского кода в фреймворке через введение сервисного слоя, иньекции и т.д.Прекрасно было бы и в виде платного интенсива, хотя я отдаю себе отчет сколько времени уходит на подготовку к подобному событию.
А может быть вы планируете еще какие то вебинары по Yii? Поделитесь плиз, чего можно ждать, или наоборот не ждать. Спасибо.

Ответить

 

Дмитрий Елисеев

Всё планирую, но не всё успеваю. Так что как повезёт :)

Ответить

 

Роман

Спасибо за ваши труды, лучшего сайта по YII не находил))
Было бы интересно узнать как правильно делать URL каталога, н-р
domain/catalog/categoryParent/categoryChild/categoryChild/name_product

Ответить

 

Вася

Дмитрий подскажите как в виджете категорий в каталоге, вывести рядом количество постов?

Ответить

 

Дмитрий Елисеев

Если без иерархии, то echo $category->getPosts()->count().

Ответить

 

Вася

Записал вот так:

'template' => '<a href="{url}" class="url-class">{label}</a>'.'<small class="badge">'.$category->getPosts()->count().'</small>,

И количество запросов к базе выросло в два раза, было 8 стало 14, я делал другим образом, как в модели поиска categorySearch.php в файл categoriesWidget добавил public $posts_count;
Потом

$categories = Category::find()
    ->select(['{{%category}}.*', 'courses_count' => new Expression('COUNT(DISTINCT {{%post}}.id)')])
    ->joinWith(['posts'], false)
    ->groupBy('{{%category}}.id')
    ->orderBy('name')->all();

и вот так:

$items[] = [
    'label' => $category->name,
    'template' => '<a href="{url}" class="url-class">{label}</a>'.'<small class="badge">'.$category->posts_count.'</small>',
    'url' => ['main/category', 'id' => $category->id],
    'active' => $this->category && $category->id == $this->category->id ? true : null,
    'items' => $this->getItemsRecursive($categories, $category->id, $current),
];

Выводиться нормально и запросов 8, так нормально или можно как то по другому?

Ответить

 

Дмитрий Елисеев

Нормально. Мы так и делали в админке в гриде категорий в этом вебинаре. Ещё учтите, что с древовидными категориями в виджете нужно считать суммарное число постов в подкатегориях.

Ответить

 

Вася

Подскажите пожалуйста как это сделать?

Ответить

 

Дмитрий Елисеев

Либо завести поле total_count и при каждом изменении постов его пересчитывать, либо каждый раз рекурсивно суммировать по parent_id, либо перевести базу на Nested Sets и считать COUNT по массивам id вложенных категорий.

Ответить

 

hajdar dushku

In root hierarchy it shows 0 posts, how we could sum with root hierarchy also?

Ответить

 

Александр

Дмитрий, спасибо за очередной качественный вебинар.
Для себя хочу уточнить один момент.
В миграциях вы создаете индексы так же для полей, на которых так же создаете FK на другое поле (или другое поле другой таблицы). Для postgres согласен. Но, ведь в mysql при создании FK автоматически создается и индекс.

Ответить

 

Дмитрий Елисеев

Просто стараюсь делать миграции, универсально работающие и на MySQL, и на PostgreSQL.

Ответить

 

Виталий Бахур

Здравствуйте Дмитрий. Хотел бы спросить а как сделать подгрузку атрубутов динамически и на лету, чтобы и валидация была. Т.е смысл такой заходишь создаешь товар, в нём указываешь категорию и у нас для каждой категории подгружает свои атрибуты. Как такое реализовать? я так понимаю в сторону behaviors надо смотреть?

Ответить

 

Дмитрий Елисеев

Либо возиться с $('#form').yiiActiveForm('add', ...), либо сделать форму двухшаговой, где на первой странице выбираем категорию, а на второй уже запоняем всё остальное.

Ответить

 

Виталий Бахур

Спасибо за совет. Ну двухшаговую делать не буду, а вот в сторону $('#form посмотрю')

Ответить

 

Олег – milalice.ru

Здравствуйте, Дмитрий. Я переделываю на Yii2 свой интернет-магазин (хочу перейти с Джумлы), но возникла сложность с выбираемыми пользователем свойствами товара (цвет и размер). В карточке товара выводятся несколько размеров и несколько цветов, но в наличии есть, например: товар 48 размера - белый, а 50 размера - красный. Подскажите, пожалуйста, как сделать связанные свойства товара: размеры со своими цветами? Как правильно создать таблицы в БД и какие прописать связи между нами?

Ответить

 

Алекс

Никак не могу понять, как могу быть записи типа $category->parent->name или 'parent.name'. Что это вообще такое? В таблице категории у нас нет поля parent. Или 'parent.name' - что это? у нас нет ни таблицы parent, ни связи такой.

Ответить

 

Алекс

Посмотрел исходники. Понял в чем дело. Не знал что $this->hasOne() и $this->hasMany() можно на тот же класс модели делать.

Ответить

 

Андрей Кушнарев

Здравствуйте Дмитрий, нашел ошибку не проявляющуюся на присутствующем наборе данных, пишу тут потому как смог решить ее только частично:
В админке в просмотре продуктов при сортировке по имени категории по факту происходит сортировка по category_id.
Если в ProductSearch поменять

$query = Product::find()->with(['tags'])->joinWith(['category', 'productTags'], false);
 ...
$dataProvider->sort->attributes['category_id'] = [
    'asc' => ['{{%category}}.name' => SORT_ASC],
    'desc' => ['{{%category}}.name' => SORT_DESC],
];
 ...

Теперь сортировка работает правильно (по имени), но возникла проблема с фильтрацией по одноименным полям которые присутствуют в product и category(в частности id и name):

Error Info: Array
(
    [0] => 23000
    [1] => 1052
    [2] => Column 'name' in where clause is ambiguous
)


как можно решить эту проблему?

Ответить

 

Андрей Кушнарев

Решил таким способом.
В ProductSearch, то что было изменено выше так и осталось и еще поменял:

$query->andFilterWhere([
    '{{%product}}.id' => $this->id,
    'category_id' => $this->category_id,
    'price' => $this->price,
    'active' => $this->active,
    '{{%product_tag}}.tag_id' => $this->tag_id,
]);

$query->andFilterWhere(['like', '{{%product}}.name', $this->name])
    ->andFilterWhere(['like', 'content', $this->content]);

Вопрос - а можно ли сделать тоже самое, но при этом не использовать имена таблиц БД, а использовать связи прописанные в модели AR?

Ответить

 

Дмитрий Елисеев

Можно вручную указать псевдонимы для каждой таблицы. А сам фреймворк имена связей в запрос не подставляет.

Ответить

 

Андрей Кушнарев

А можно поподробнее и с примером? А то что-то не совсем понятно.

Ответить

 

Дмитрий Елисеев

Указать псевдонимы:

$query = Product::find()
    ->from(['product' => Product::tableName()])
    ->joinWith('category category');

И использовать псевдоним 'product.id' вместо имени таблицы '{{%product}}.id'.

Ответить

 

Андрей Кушнарев

Спасибо, вот как раз то что нужно было, не понимал как прописать псевдоним для продуктов...

Ответить

 

Shvarz

Спасибо за урок , очень классно, разобрался уже связал 3 таблицы... Но теперь все, копипаст закончился, теперь думать самому приходится.

Таблица users связана с таблицей event, мне нужно чтобы при создании нового event я вручную не вбивал айдишник пользователся. скорее всего нужно в представлении _form, вместо

<?= $form->field($model, 'user_id')->textInput() ?>

сделать

'user_id'=> User::($model, 'id')

но у меня с синтаксисом беда помогите ....

Ответить

 

Дмитрий Елисеев

Если пользователей мало, то можно выпадающим списком:

<?= $form->field($model, 'user_id')->dropDownList(ArrayHelper::map(User::find()->asArray()->all(), 'id', 'username')) ?>
Ответить

 

Shvarz

Нет, Дмитрий вы не поняли меня, мне нужно чтобы он выбирал текущего залогиненного пользователя и сразу добавлял в поле user_id в таблице event.

т.е кто создал событие того айдишник и добавляется, что это он создатель.

Ответить

 

Shvarz

Тут походу функцию надо писать , хотя бы пример какой-нибудь....

Ответить

 

Shvarz

Yii::$app->user->id - полчение текущего айди пользователя
и потом нужно в представлении _form или create записать в поле user_id это значение

Ответить

 

Дмитрий Елисеев

Впишите в контроллере:

$model = new Event();
$model->user_id = Yii::$app->user->id;

и всё.

Ответить

 

Shvarz

спс, я был близок, во второй строке у меня небыло в начале $model...
Жесть столько времени убито, надо синтакисис учить...

Ответить

 

Shvarz

Вопрос еще один созрел: Если я создаю,допустим событие в котором нужно добавить файлы.
Как загрузить сразу файлы в момент формирования события? (хочу хранить файлы, логи все в бд)
Какие способы выбрать и правильно ли я вообще мыслю?
Способы:
1)сразу создавать событие с пустыми параметрами, но только чтобы был айди события и сразу загружать файлы к событию?
2)Или сделать временную таблицу туда записывать файлы ?

Ответить

 

Shvarz

Когда я нажимаю на кнопку создать событие т.е actionCreate событие уже в базе создается с айдишником или нет? Извините за глупые вопросы понимаю как понимаю ...

Ответить

 

Дмитрий Елисеев

Айдишник добавляется после save(). Если же нужно загрузить по Ajax до сохранения модели, то обычно грузим всё во временную папку и сохраняем имена файлов в сессию. А в момент сохранения уже перемещаем эти файлы куда надо.

Ответить

 

Shvarz

Спс, для начала пойду путем создания 2-ой формы, а потом когда всю структуру выведу буду оптимизировать...

Ответить

 

Shvarz

таблица Event связана с таблицей User:

$this->addForeignKey('fk-event-user_id','{{%event}}','user_id','{{%user}}','id','CASCADE','RESTRICT');
$this->addForeignKey('fk-event-isp_id','{{%event}}','isp_id','{{%user}}','id','CASCADE','RESTRICT');

В представлении event\index вывожу вместо параметров 'user_id' и 'isp_id' следующее:

 [
   'attribute' => 'user_id',              
     'value'=>function(Event $event){
           return $event->user->username;
                }         
  ],

  [
   'attribute' => 'isp_id',              
        'value'=>function(Event $event){
             return $event->user->username;
                }             
  ],

Он выводит имена , но если потом сравнить с id , то неправильно , одного айди вообще не выводит , вместо него повторяет имя старого, что я не так ?

Ответить

 

Shvarz

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

Ответить

 

Дмитрий Елисеев

Ну так и выводите из разных:

$event->user->username
$event->isp->username

Или проще:

[
    'attribute' => 'user_id',              
    'value' => 'user.username';
],
[
    'attribute' => 'isp_id',              
    'value' => 'isp.username';
],
Ответить

 

Shvarz

Заработало,спс. Ток я не вкурю , почему так?

Ответить

 

Shvarz

типо отбрасывается _id ?

Ответить

 

Дмитрий Елисеев

В модели Event генерируются методы getUser() и getIsp() с hasOne либо hasMany. Это и есть связи $event->user и $event->isp.

Ответить

 

Shvarz

Ага догнал, спс...

Ответить

 

Shvarz

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

return $this->last_name.' '.$this->name.' '.substr($this->patronymic,1,1).' '.$this->username;

но он какой -то фигурный символ выводит вместо первой заглавной буквы отчества.
Как я понимаю нужно в представлении через аррайхелпер , но там тоже выведет только полностью функцию...
Получается либо в модели как-то парсить , либо в представлении через хелпер все параметры перечислять....

$event=Event::find(),
$ggstr=substr($event->user->last_name,0,1),
'value'=>ArrayHelper::getValue(function (Event  $event) {
return $event->user->name . ' ' . $event->user->last_name; })
Ответить

 

Shvarz

Нашел проблему, кому интересно решается через mb_substr

Ответить

 

Shvarz

Хочу сделать так :gри создании заявки ( Event), я выбираю из >dropDownList отдел( Department), после выбора отдела у меня выбирается 1 исполнитель только из этого выбранного отдела.
Но не работает , брал материал:

1) http://quabr.com/34989819/yii2-dependent-dropdown-list
2) https://www.youtube.com/watch?v=ZepxKw8VA7w

в итоге переделал под свой вариант и вот что получилось , но не работает при выборе исполнителя...

ProfileController:

    public function actionLists($id)
    {        
        $countProfile=User::find()->where(['department_id'=>$id])->count();
        $profile=User::find()->where(['department_id'=>$id])->all();
        if ($countProfile > 0)
        {
            foreach ($profile as $user1) {
                echo "<option value='" . $user1->user_id. "'>" . $user1->fioName . "</option>";
            }
        }
        else
        {
            echo "<option> - </option>";
        }
    }

event-form:

 <?php $form = ActiveForm::begin(); ?>
    <?= $form->field($model, 'name')->textInput(['maxlength' => true]) ?>

    <?php
    $dataDEP = ArrayHelper::map(Department::find()->asArray()->all(), 'id', 'name');
    echo $form->field($model, 'dep_id')->dropDownList($dataDEP,
        [
            'prompt'=>'Selected department',
            'onchange'=>'
            
            $.post( "'.Yii::$app->urlManager->createUrl('profile/lists?id=').'"+$(this).val(), function ( data ){           
              $( "select#profile-dep_id" ).html(data);
               });
        ']);

    echo "<br>";
    $dataUSER = ArrayHelper::map(User::find()->asArray()->all(), 'id', 'fioName');
    echo $form->field($model, 'isp_id')->dropDownList($dataUSER,
        [
            'prompt'=>'Selected user',
            'id'=>'dep_id']
    ); ?>

Что не так , думаю либо со связями проблема, либо еще что-то в модель нужно записать ...
Помогите.....

Ответить

 

Shvarz

Все сделал, без всяких kratikov =)

Ответить

 

Shvarz

Снова я. Привет Дмитрий.

public function actionSubmission()
    {$comment=new Comment();
        $comment->name=$string;
        $comment->user_id=Yii::$app->user->id;
        $comment->event_id='71';
        $comment->save();
        return $this->render('submission', [
                     'stringHash' => $string,
        ]);
    }


эта функция в EventController , как мне получить значение текущего айди сообщения , чтобы ($comment->event_id='71 ) не присваивать вручную?
Заранее спасибо...'

Ответить

 

Shvarz

Т.е. У меня есть модель Event и модель Comment, через аякс я добавлю в моделе евент комментарии

Ответить

 

Дмитрий Елисеев
public function actionSubmission($id)
{
    ...
    $comment->event_id = $id;
    ...
}

И передавать ?id=71 в адресе из Ajax.

Ответить

 

Shvarz

Не это все я пробовал , результат тот же , может я не правильно объяснил , сейчас попробую точнее.
Я использую не чистый аякс а pjax:
представление :event- submission

......
 <?php Pjax::begin(['enablePushState' => false]); ?>
            <?= Html::beginForm(['event/submission?id=71'], 'post', ['data-pjax' => '', 'class' => 'form-inline']); ?>
........... 
    <?= Html::endForm() ?>
    </div>
    <div class="col-md-5">
        <h3><?= $stringHash ?></h3>
    <?php Pjax::end(); ?>


Фишка в том, что если я не в view/71 , а допустим в view/63 захожу и там пытаюсь добавить , то он мне опять добавляет комментарий с id_event=71.

['event/submission?id=$id'], как-то так надо , он не видит в какой текущий вием я зашел , как мне определить в каком я виеме нахожусь, чтобы он вместо 71 поставил уже 63..

Ответить

 

Shvarz

public function actionSubmission($id) storm подчеркивает $id , что аргумент отсутствует, как мне его правильно передать в представлении ?

Ответить

 

Shvarz

И если можно скиньте ссылку на примеры рабочие , где чистый аякс, потому -что походу мне придется кучу функций писать , откуда черпать материал , заранее спасибо...

Ответить

 

Дмитрий Елисеев
Html::beginForm(['event/submission, 'id' = $model->id], ...) ?>
Ответить

 

Shvarz

Ихаааа, заработало, только надо там не равно , а (указатель) => и еще добавить

'model' => $this->findModel($id),


Спасибо Дмитрий , без тебя хз сколько еще бы промучился...

Ответить

 

Олег – milalice.ru

Здравствуйте, Дмитрий. Я переделываю на Yii2 свой интернет-магазин (хочу перейти с Джумлы), но возникла сложность с выбираемыми пользователем свойствами товара (цвет и размер). В карточке товара выводятся несколько размеров и несколько цветов, но в наличии есть, например: товар 48 размера - белый, а 50 размера - красный. Подскажите, пожалуйста, как сделать связанные свойства товара: размеры со своими цветами? Как правильно создать таблицы в БД и какие прописать связи между нами?

Это вопрос я уже задавал месяца 2 назад, но ответа не получил. Интересно, получу ли я его сейчас?
Или все так сложно?

Ответить

 

Дмитрий Елисеев

Здравствуйте. Добавьте таблицу availability с полями product_id, size_id, color_id и count. Будут обычные связи к первичным ключам.

Ответить

 

Олег

Здравствуйте, Дмитрий. Делаю этот урок за Вами. и У меня не получается вывод хлебных крошек со вложенными категориями. Выводится в крошках только: Главная / Catalog. Вот код, который я воспроизвел из урока.

$this->title = $category->name;
$this->params['breadcrumbs'][] = ['label' => 'Catalog', 'url' => ['idex']];

$crumbs = [];
$parent = $category;
while ($parent = $parent->parent) {
    $crumbs[] = ['label' => $parent->name, 'url' => ['category', 'id' => $parent->id]];
    echo $crumbs;
}
$this->params['breadcrumbs'] = array_merge($this->params['breadcrumbs'], array_reverse($crumbs));
$this->params['breadcrumbs'][] = $this->title;
Ответить

 

Олег

Разобрался, не передал в вид $category из экшена.

Ответить

 

Александр

Подскажите как отсортировать модель по связной таблице с помощью yii\data\Sort

Ответить

 

Дмитрий Елисеев

С 3:02:09 на видео.

Ответить

 

Александр

Спасибо помогло)

Ответить

 

Александр Исаев

Дмитрий, как можно вывести только категории, в которых есть продукты с определённым тегом?

Ответить

 

Дмитрий Елисеев
SELECT * FROM category INNER JOIN product ON ... INNER JOIN product_tag ON ... WHERE producr_tag.tag_id = 42 GROUP BY category.id
Ответить

 

Сергей Скапа

Есть три таблицы: для групп, для установок и для оборудования.
В третью передаются id из первых двух, делая вложенность.
Фильтрация идет:

//'groups_id',
            [
                'attribute' => 'groups_id',
                'filter' => Groups::find()->select(['name', 'id'])->indexBy('id')->column(),
                'value' => 'groups.name',
            ],
//'lines_id',
            [
                'attribute' => 'lines_id',
                'filter' => Lines::find()->select(['name', 'id'])->indexBy('id')->column(),
                'value' => 'lines.name',
            ],

Но есть проблема, как отсеять часть записей 'lines_id' после выборки в первом фильтре 'groups_id'
Есть ли решение без подключения сторонних модулей?

Ответить

 

Дмитрий Елисеев
Lines::find()->alias('t')->andWhere([
    'exists',
    Posts::find()->alias('p')->andWhere([
        'p.lines_id' => new Expression('t.id'),
        'p.groups_id' => $searchModel->groups_id
    ])
])->select(['t.name', 't.id'])
Ответить

 

Сергей Скапа

Дмитрий, подскажите почему может ругаться на класс Posts?

Ответить

 

Дмитрий Елисеев

Потому, что его надо на ваш класс заменить.

Ответить

 

Сергей Скапа

получил ошибку: "array_merge(): Argument #2 is not an array"

yii\base\Widget::widget(['dataProvider' => yii\data\ActiveDataProvider, 'filterModel' => backend\models\admin\search\EquipmentsSearch, 'columns' => [0 => ['class' => 'yii\grid\SerialColumn'], 1 => 'id', 2 => ['attribute' => 'groups_id', 'filter' => [4 => 'Blowing', 3 => 'CIP', 5 => 'Engineering', 2 => 'Packing', ...], 'value' => 'groups.name'], 'attribute' => 'lines_id', ...]])
'attribute' => 'lines_id',
'filter' => Lines::find()->alias('t')->andWhere([
'exists',
Equipments::find()->alias('p')->andWhere([
'p.lines_id' => new Expression('t.id'),
'p.groups_id' => $searchModel->groups_id
])
])->select(['t.name', 't.id']),
Ответить

 

Дмитрий Елисеев
...->indexBy('id')->column(),
Ответить

 

Сергей Скапа

Дмитрий, спасибо работает:

[
'attribute' => 'lines_id',
       'filter' => Lines::find()->alias('t')->andWhere([
                     'exists',
                    Equipments::find()->alias('p')->andWhere([
                        'p.lines_id' => new Expression('t.id'),
                        'p.groups_id' => $searchModel->groups_id])
                    ])-> select(['t.name', 't.id'])->indexBy('id')->column(),
       'value' => 'lines.name',
 ],

но из-за того что строка: 'value' => 'lines.name', не обрабатывается в index.php -> не подставляется значение. Если её убрать то id подставляет. Что делаю не так?

Ответить

 

Дмитрий Елисеев

Видимо проблема со связью.

Ответить

 

Сергей Скапа

как я понимаю, то значение 'value' = null, но как его правильно передать?

Ответить

 

Сергей

Дмитрий, при добавлении к Товарам Тегов с использованием checkBoxList Вы рекомендуете выполнять валидацию путем:

['tagsArray', 'each', 'rule' => ['integer']]

Однако, checkbox всегда отправляет данные типа string, и потому данный валидатор ВСЕГДА выдаст ошибку.

Подскажите, пожалуйста, как правильно проверить тип данных в таком случае?

Ответить

 

Дмитрий Елисеев

Если нужен обязательный checkbox, то:

['field', 'compare', 'value' => '1'],
Ответить

 

Сергей

На странице редактирования товара есть checkboxList 'tagsArray', который возвращает массив tagsArray, содержащий id-шники тегов данного товара.
Мне нужно убедиться, что из формы пришли именно integer. Вы в этом вебинаре рекомендуете использовать ['tagsArray', 'each', 'rule' => ['integer']], однако checkbox в значении value всегда возвращает string. То есть, правило валидации в таком виде вернет ошибку - ведь из формы всегда будет приходить массив string.
Я вышел из положения, применив правило валидации ['arrayTags', 'each', 'rule' => ['match', 'pattern' => '/^\d+/']], но мне интересно, есть ли, возможно, другое, концептуально правильное решение.
Спасибо.

Ответить

 

Дмитрий Елисеев

> правило валидации в таком виде вернет ошибку.

Не вернёт, так как все данные из формы всегда приходят как string и данный валидатор это учитывает.

Ответить

 

Алексей

Отличный вебинар. Спасибо! Много полезной инфы.

Как модифицировать данный запрос, чтобы считалось количество товаров не только в самой рубрике, но и для родительской рубрики считалось количество товаров во всех дочерних рубриках?

$query = Category::find()
            ->select(['{{%category}}.*', 'products_count' => new Expression('COUNT({{%product}}.id)')])
            ->joinWith(['products'], false)
            ->groupBy('{{%category}}.id')
            ->with(['parent']);
Ответить

 

Дмитрий Елисеев

1) Получить массив $ids из id текущей и всех её дочерних рубрик.
2) Посчитать Product::find()->andWhere(['category_id' => $ids])->count().

Ответить

 

Олег

привет.
есть такой вопрос по связям в Yii2
у меня есть 2 связи:

public function getPlaceWashere() {
    return $this->hasMany(PlaceWashere::className(), ['place_id' => 'id'])
            ->where(['washere' => 1]);
}

public function getPlaceWashereUser() {
    return $this->hasMany(User::className(), ['id' => 'user_id'])->via('placeWashere');
}

в ходе выполнения запроса с жадной загрузкой связи PlaceWashereUser т.е. ->with(['placeWashereUser'])
происходит выполнение 2 запросов
первый выбирает ключи
второй - уже то что и хочется - подставляя ключи в условие ... IN (.. ключи ..)

такое поведение мне не подходит - т.к. связей очень много здесь PlaceWashere::className()
существует ли возможность указать что ключи выбирать не нужно - а нужно использовать первую связь как подзапрос?

спасибо за ответ

Ответить

 

Дмитрий Елисеев

Yii2 всегда подгружает связи по IN (...).

Ответить

 

Олег

спасибо. уже разобрался )
$this->hasMany(... можно и подзапросы делать используя ActiveQuery который веренет геттер

Ответить

 

Алексей

Здравствуйте. Мне нужно при создании материала (например статья) загрузить изображения через ajax и привязать их к этому материалу, но у материала пока нет id, к которому я собираюсь привязывать картинки. Для пользователя заполнение полей материала и загрузка картинок должны быть на одной странице. Я решил сделать так:
Пользователь жмет "добавить статью", срабатывает экшн создания пустой записи статьи в базе и сразу перебрасывает на редактирование этой статьи. Таким образом id уже есть, а пользователь думает, что он создает новую статью хотя на самом деле уже редактирует.

Подскажите, может есть какой-нибудь правильный вариант привязки изображений?

Ответить

 

Дмитрий Елисеев

Ещё можно сохранять файлы во временную папку и записывать их имена в сессию. А при сохранении статьи уже перемещать в нормальную папку и привязывать по id.

Ответить

 

Артем

Здравствуйте, Дмитрий. У меня такой вопрос, если допустим у меня есть в продуктах столбец Category_name - это та категория, к которой мне нужно привязать товар. Как можно по имени Category_name связать товар с категорией?

Ответить

 

Дмитрий Елисеев

Можно в миграции добавить поле category_id и выполнить запрос UPDATE products p SET p.category_id = (SELECT c.id FROM categories c WHERE c.name = p.category_name).

Ответить

Оставить комментарий

Войти | Завести аккаунт | Войти через


(никто не увидит)





Можно использовать теги <p> <ul> <li> <b> <i> <a> <pre>