Вопрос по связи через промежуточную таблицу hasMany()->viaTable()

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
surrealistic_pillow
Сообщения: 39
Зарегистрирован: 2016.11.22, 18:14

Вопрос по связи через промежуточную таблицу hasMany()->viaTable()

Сообщение surrealistic_pillow »

Возникла необходимость сделать надстройку над cms (учет пользователей системы дистанционного обучения Moodle)

Проблема возникла при попытке создать связь с помощью viaTable

Вот, имеются такие таблицы, задействованные поля я привел

mdl_backend_user таблица учетных записей слушателей
id
mdl_user_id

mdl_backend_user_requests таблица заявок слушателей на обучение
id
user_id

mdl_backend_module_entries таблица для того чтобы соотносить id заявок и модули на которые записались пользователи (одной заявке может соответствовать много модулей)
request_id
module_id

mdl_user_lastaccess (дефолтная таблица cms moodle, userid соответствует mdl_user_id из таблицы mdl_backend_user. courseid соответствует module_id из таблицы mdl_backend_module_entries)
userid
courseidt
timeaccess

соответственно мне надо вытянуть все значения timeaccess из mdl_user_lastaccess которые соответствуют определенному юзеру и модулям на которые он записался.
и получить максимальное ззначение (иными словами last_activity по всей заявке а не по каждому модулю)

Устанавливаю связи в модели Request (таблица mdl_backend_user_requests):

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

	public function getAccount(){ //устанавливаю связь с таблицей mdl_backend_user
		return $this->hasOne(Account::className(), ['id' => 'user_id']);		
	}

	public function getLastActivity(){ //устанавливаю связь с таблицей mdl_user_lastaccess через таблицу mdl_backend_module_entries
		return $this->hasMany(MdlUserLastaccess::className(), ['courseid' => 'module_id', 'userid' => $this->account->mdl_user_id])
		->viaTable('mdl_backend_module_entries',['request_id' => 'id']);
	}	
во вьюхе (index.php)

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

	[
		'attribute' => 'lastActivity',
		'value' => function($model) {
			return $model->getLastActivity()->one()->timeaccess; 
		},
	],	
получаю ошибку

PHP Notice – yii\base\ErrorException
Undefined offset: 145

Когда меняю в модели геттер, убираю условие 'userid' => $this->account->mdl_user_id
то все работает, выводит в gridview значение timeaccess, но мне надо именно с привязкой к слушателю.

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

	public function getLastActivity(){ //устанавливаю связь с таблицей mdl_user_lastaccess через таблицу mdl_backend_module_entries
		return $this->hasMany(MdlUserLastaccess::className(), ['courseid' => 'module_id'])
		->viaTable('mdl_backend_module_entries',['request_id' => 'id']);
	}	
caHek2x
Сообщения: 1242
Зарегистрирован: 2016.04.12, 20:41

Re: Вопрос по связи через промежуточную таблицу hasMany()->viaTable()

Сообщение caHek2x »

'userid' => $this->account->mdl_user_id
это условие ... а не связь .. .поэтому загоняйте в onCondition ... хотя работать будет но это тоже не хорошо ...
-----------
чтото я не обратил внимание .. а сейчас вчитался ...
getLastActivity()
это же связь ...
return $model->getLastActivity()->one()->timeaccess;
так странно конечно делать ...
surrealistic_pillow
Сообщения: 39
Зарегистрирован: 2016.11.22, 18:14

Re: Вопрос по связи через промежуточную таблицу hasMany()->viaTable()

Сообщение surrealistic_pillow »

caHek2x
Я недавно с Yii знаком, а как надо, можете подсказать?
surrealistic_pillow
Сообщения: 39
Зарегистрирован: 2016.11.22, 18:14

Re: Вопрос по связи через промежуточную таблицу hasMany()->viaTable()

Сообщение surrealistic_pillow »

сделал так что нужная мне дата-время выводится в нужном столбце в gridview. однако же не работает сортировка и поиск по этому вычисляемому полю
в модели

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

	public function getLastActivity($request_id, $mdl_user_id) {
		$result0 = Yii::$app->db->createCommand("SELECT `module_id` FROM `mdl_backend_module_entries` WHERE request_id = ".$request_id)->queryAll();
		$checked_modules = ArrayHelper::toArray($result0);
		
		$query = "SELECT `timeaccess` FROM `mdl_user_lastaccess` WHERE userid = ".$mdl_user_id;
		$query .= " AND (";
		
		foreach($checked_modules as $module){
			$query .= "courseid = ".$module['module_id'];
			if($module!==end($checked_modules)) $query .= " OR ";
		}
		
		$query .= ")";
		
		$result1 = Yii::$app->db->createCommand($query)->queryAll();
		
		$accessArr = ArrayHelper::toArray($result1);
		
		if(count($accessArr) > 0)
			$last_activity = (int)max($accessArr)['timeaccess'];
		
		else $last_activity = '-';
		
		return $last_activity;
	}
в index.php

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

			[
                'attribute' => 'lastActivity',
                'value' => function($model) {
					if(!empty($model->account)&&is_int($model->getLastActivity($model->id, $model->account->mdl_user_id)))
						return date('d.m.Y H:i:s', $model->getLastActivity($model->id, $model->account->mdl_user_id));	
					else return '-';
                },
				'format'=>'raw',
            ],
Скажите, реально ли как-нибудь, не устанавливая связи получить сортировку и поиск по этому полю, значение которого я вычисляю сам?
Со связью уже всю голову сломал не выходит каменный цветок
caHek2x
Сообщения: 1242
Зарегистрирован: 2016.04.12, 20:41

Re: Вопрос по связи через промежуточную таблицу hasMany()->viaTable()

Сообщение caHek2x »

Что со связью не вышло ? OnCondition делали ? Для связи не надо вызывать one() просто без гет к ней обращайтесь как к полю ... А метод который вы нагородили, спрашивается зачем вам yii если вы так запросы описывает .... Мне кажется вам стоит пройтись по документации а потом что-то делать .. не зная возможностей инструмента сложно с ним работать
surrealistic_pillow
Сообщения: 39
Зарегистрирован: 2016.11.22, 18:14

Re: Вопрос по связи через промежуточную таблицу hasMany()->viaTable()

Сообщение surrealistic_pillow »

изменил index.php

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

[
                'attribute' => 'lastActivity',
                'value' => function($model) {
                    return $model->lastActivity->timeaccess; 
                },
],	
по-прежнему ошибка
PHP Notice – yii\base\ErrorException
Undefined offset: 145

когда вставил второе условие в onCondition:

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

	
	public function getLastActivity(){//getMdlUserLastaccess
		//if(is_object($this->account)){
		return $this->hasMany(MdlUserLastaccess::className(), ['courseid' => 'module_id',/* 'userid' => $this->account->mdl_user_id*/])
		->viaTable('mdl_backend_module_entries',['request_id' => 'id'])->onCondition(['userid' => $this->account->mdl_user_id]);
		//}
	}
PHP Notice – yii\base\ErrorException
Trying to get property of non-object

подчеркивает строку из index.php
return $model->lastActivity->timeaccess;
Последний раз редактировалось surrealistic_pillow 2017.03.01, 14:30, всего редактировалось 1 раз.
caHek2x
Сообщения: 1242
Зарегистрирован: 2016.04.12, 20:41

Re: Вопрос по связи через промежуточную таблицу hasMany()->viaTable()

Сообщение caHek2x »

у вас до этого было в связи
'userid' => $this->account->mdl_user_id
вот это и вгоняйте в onCondition ... это костыль но вам пойдет ...

hasMany вернет вам массив ... а вы пытаетесь у массива делать ->timeaccess ... еще раз настою ознакомиться с документацией ... и вникнуть что там пишут .. .в особенности про hasOne и hasMany ...
surrealistic_pillow
Сообщения: 39
Зарегистрирован: 2016.11.22, 18:14

Re: Вопрос по связи через промежуточную таблицу hasMany()->viaTable()

Сообщение surrealistic_pillow »

Сделал таким образом в модели:

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

	
	public function getLastActivity(){
		if(!empty($this->account)){
			return $this->hasMany(MdlUserLastaccess::className(), ['courseid' => 'module_id'])
				->onCondition(['userid' => $this->account->mdl_user_id])
				->viaTable('mdl_backend_module_entries',['request_id' => 'id']);
		}
	}
и в index.php

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

			[
                'attribute' => 'lastActivity',
                'value' => function($model) {
					foreach(ArrayHelper::toArray($model->lastActivity) as $elem){
						$finalArr[]=$elem['timeaccess'];						
					}
					if(!empty($finalArr)&&max($finalArr)>0)
						return date('d.m.Y H:i:s',max($finalArr));
					else return '-';
                },
            ],
Все выводится.

Но для меня так и остается загадкой, как заставить Yii сортировать по этому полю?

когда в модели RequestSearch добавляю

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

		$dataProvider->setSort([
				'lastActivity' => [
					'asc' => ['mdl_user_lastaccess.timeaccess' => SORT_ASC],
					'desc' => ['mdl_user_lastaccess.timeaccess' => SORT_DESC],
					'label' => 'Посл. активность',
					'default' => SORT_ASC
				],
		]);
туда же

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

if(!empty($this->lastActivity)){
	$query->joinWith(['moduleEntries'])
	->joinWith(['lastActivity']);		
}
Ошибка
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'mdl_user_lastaccess.timeaccess' in 'order clause'
The SQL being executed was: SELECT * FROM `mdl_backend_student_requests` WHERE firstname LIKE "%%" OR middlename LIKE "%%"OR lastname LIKE "%%" ORDER BY `mdl_user_lastaccess`.`timeaccess` LIMIT 20

понятно - когда lastActivity пуст (активности не было), то условие if(!empty($this->lastActivity)){
не пропускает joinWith()

если убрать условие то вываливается

Invalid Parameter – yii\base\InvalidParamException
app\models\Request has no relation named "lastActivity".
caHek2x
Сообщения: 1242
Зарегистрирован: 2016.04.12, 20:41

Re: Вопрос по связи через промежуточную таблицу hasMany()->viaTable()

Сообщение caHek2x »

вы сильно мудрите ... в связи условий быть не должно ...
я сразу сказал что onCondition в связи это плохо ... если просто достать то еще ладно но если использовать это дело еще в join и тд то это плохо ...
после 00 буду дома еще раз пересмотрю вашу задачу и может чтото подскажу ...
caHek2x
Сообщения: 1242
Зарегистрирован: 2016.04.12, 20:41

Re: Вопрос по связи через промежуточную таблицу hasMany()->viaTable()

Сообщение caHek2x »

я модели обозвал по названию таблиц .. .мне так легче ориентироваться ....
сделать связь через 3 таблицы не получится ... вроде на эту тему issues на гите есть ...
делаем так: в модели MdlBackendUserRequests
делаем две связи:

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

public function getUser()
{
    return $this->hasOne(MdlBackendUser::className(), ['id' => 'user_id']);
}

public function getModuleEntries()
{
    return $this->hasMany(MdlBackendModuleEntries::className(), ['request_id' => 'id']);
}
и делаем метод:

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

public function getLastAccessUser(){
        if (count($this->moduleEntires)>0 && $this->user){
            $modules_id = ArrayHelper::getColumn($this->moduleEntires, 'module_id');
            $find = MdlUserLastaccess::find()->where(['courseid'=>$modules_id, 'userid'=>$this->user->mdl_user_id])->orderBy(['timeaccess'=>SORT_DESC])->one();
            if ($find) return $find->timeaccess;
        }
        return null;
    }
согласитесь выглядит изящнее чем ваша каша под названием public function getLastActivity($request_id, $mdl_user_id)
ну а в вашем value соответственно делаете

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

'value' => function($model) {
    $last = $model->getLastAccessUser();
    return $last===null?"нет данных":$last;
},
а вот сортировка и фильтрация ... на основе этого надо чтото подумать ...
http://stackoverflow.com/questions/2857 ... n-database
Последний раз редактировалось caHek2x 2017.03.02, 00:56, всего редактировалось 1 раз.
caHek2x
Сообщения: 1242
Зарегистрирован: 2016.04.12, 20:41

Re: Вопрос по связи через промежуточную таблицу hasMany()->viaTable()

Сообщение caHek2x »

в модель MdlBackendModuleEntries
добавляете

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

public function getLastAccesses()
{
    return $this->hasMany(MdlUserLastaccess::className(), ['courseid' => 'module_id']);
}
в и ваш поиск добавляете

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

$find = MdlBackendUserRequests::find();

$find->joinWith(['user as users']);
$subQuery = MdlBackendModuleEntries::find()
    ->select('request_id, userid, MAX(timeaccess) timeaccess')
    ->joinWith("lastAccesses")
    ->groupBy(['request_id', 'userid']);

$find->leftJoin(['lastaccess'=>$subQuery], [
    'lastaccess.request_id' => new Expression(MdlBackendUserRequests::tableName().'.id'),
    'lastaccess.userid'=> new Expression('users.mdl_user_id')
]);
//ну а дальше $find - select - lastaccess.timeaccess
и не надо дергать метод который писал выше (getLastAccessUser()) и фильтр так можно настроить и сортировку ...
---------------
если по каким то соображениям вам не нравится делать join то можно

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

$find = MdlBackendUserRequests::find();

$find->joinWith(['user as users']);
$find->select("*");
$find->addSelect(
    ['lasttimeaccess'=>
        MdlBackendModuleEntries::find()
            ->alias("modules")
            ->join("join", MdlUserLastaccess::tableName()." as lastaccess", "lastAccess.courseid = modules.module_id")
            ->where(['modules.request_id'=>new Expression(MdlBackendUserRequests::tableName().".id")])
            ->andWhere(['lastAccess.courseid'=>new Expression("modules.module_id")])
            ->orderBy(['timeaccess'=>SORT_DESC])->select("timeaccess")->limit(1)
    ]
);
если спросите что с этим делать и куда подставлять - даже не отвечу ... (т.к. все таки я не поленился и час клепал вам код а вы не можете даже немного вникнуть ...)
---------------------
пример как такое использовать в сортировке и фильтрации есть выше в ссылке
surrealistic_pillow
Сообщения: 39
Зарегистрирован: 2016.11.22, 18:14

Re: Вопрос по связи через промежуточную таблицу hasMany()->viaTable()

Сообщение surrealistic_pillow »

caHek2x
спасибо большое за ваши труды!

вывести дату-время у меня получилось, с сортировкой и фильтром придется разбираться, напишу потом сюда результат
surrealistic_pillow
Сообщения: 39
Зарегистрирован: 2016.11.22, 18:14

Re: Вопрос по связи через промежуточную таблицу hasMany()->viaTable()

Сообщение surrealistic_pillow »

вобщем, не получилось разобраться, времени нет, а результат требуют.
сделал костыль: добавил в таблицу mdl_backend_user_request поле last_activity и заполняю его триггером навешанным на таблицу mdl_user_lastaccess, который выбирает последнюю активность из нескольких записей соответствующих пользователю и модулю.
Ответить