Построитель простейших SQL запросов на примере выборок модуля File

Может кто-то и не знал, но в XText есть построитель простых SQL запросов, который я использую в мапперах этого плагина.

Согласитесь, как некрасиво переопределять метод маппера какого-либо модуля, ведь при этом полностью переписывается запрос, содержащийся в этом методе. А если его переопределят два плагина? Да, правильно, отработает только последний :)

Ради красивости решения, я придумал объект модифицируемого запроса в виде Entity. Называется он PluginXtext_ModuleSql_EntityQuery.

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

Для наглядности, в этом маппере, методы разделены на два типа: первые отдают объект Sql-запроса, вторые же — его исполняют и возвращают результат методу модуля.

Вот три метода маппера для затравки:

	/**
	 * @return PluginXtext_ModuleSql_EntityQuery
	 */
	public function GetFilesSql(){
		$oQ = func_xtext_sql()
			->addFromTable(Config::Get('plugin.xtext.table.file'), 'f')
			->addSelect('f.*')
			->addCacheTag(
				'xtext_file'
			)
			->setCacheTime(60*60*24)
		;
		return $oQ;
	}
	
	/**
	 * @return PluginXtext_ModuleSql_EntityQuery
	 */
	public function GetFilesByIdArraySql($aFileId){
		if(!is_array($aFileId)
		|| !($aFileId = array_filter($aFileId))){
			return;
		}
		$oQ = $this->GetFilesSql()
			->addWhere('file_id', 'f.file_id in (?a)')
				->A($aFileId)
			->addCacheTag(
				func_build_cache_keys($aFileId, 'xtext_file_')
			)
		;
		return $oQ;
	}
	
	public function GetFilesByIdArray($aFileId){
		if(!($oQ = $this->GetFilesByIdArraySql($aFileId))){
			return array();
		}
		$aFiles = array();
		if($aRows = $oQ->Exec()){
			foreach($aRows as $aRow){
				$aFiles[$aRow['file_id']] = Engine::GetEntity('PluginXtext_File', $aRow);
			}
		}
		return $aFiles;
	}


  1. GetFilesSql — задает общий вид выборки файлов, устанавливает общий тег кеша и его таймаут
  2. GetFilesByIdArraySql — добавляет к объекту запроса условие where с соответствующим аргументом для плейсхолдера ?a, еще N кеш-тегов и так же возвращает слегка модифицированный объект запроса
  3. GetFilesByIdArray — исполняет запрос, полученный от GetFilesByIdArraySql, создает список объектов файловых записей и возвращает их. Это тот самый конечный метод, к которому можно обращаться из метода модуля.

Путем переопределения одного из приведенных выше методов *Sql маппера можно докинуть туда поля из других таблиц LEFT JOIN'ом, для которого так же предусмотрен метод
public function addLeftJoinTable($sTable, $sShort = null, $sOn = null)
где
  • $sTable — имя подключаемой таблицы
  • $sShort — алиас таблицы для запроса
  • $sOn — условие присоединения
После подключения JEFT JOIN'ом таблицы, не забывайте дунуть добавить поля выборки для подключенной таблицы
public function addSelect($sWhat, $sShort = null)
где
  • $sWhat — имя добавляемого в выборку поля в виде 'алиас_таблицы.имя_поля'
  • $sShort — алиас выбираемого поля, если его надо переименовать в результате выборки

Построение фильтров


Сейчас будет огромная тонна кода :) Метод построения фильтра для получения списка файлов:

	/**
	 * @return PluginXtext_ModuleSql_EntityQuery
	 */
	public function AddFilterGetFilesSql($oQ, $aFilter){
		if(!$oQ){
			return;
		}
		if(!empty($aFilter['id'])){
			$oQ
				->addWhere(
					'file_id',
					is_array($aFilter['id'])
						? 'f.file_id in (?a)'
						: 'f.file_id=?d'
					)
				->A($aFilter['id'])
				->addCacheTag(
					is_array($aFilter['id'])
						? func_build_cache_keys($aFilter['id'], 'xtext_file_')
						: 'xtext_file_'.$aFilter['id']
				)
			;
		}
		if(!empty($aFilter['user_id'])){
			$oQ
				->addWhere(
					'user_id',
					is_array($aFilter['user_id'])
						? 'f.file_user_id in (?a)'
						: 'f.file_user_id=?d'
					)
				->A($aFilter['user_id'])
				->addCacheTag(
					is_array($aFilter['user_id'])
						? func_build_cache_keys($aFilter['user_id'], 'xtext_file_user_')
						: 'xtext_file_user_'.$aFilter['user_id']
				)
			;
		}
		if(!empty($aFilter['ext'])){
			$oQ
				->addWhere(
					'ext',
					is_array($aFilter['ext'])
						? 'f.file_ext in (?a)'
						: 'f.file_ext=?'
					)
				->A($aFilter['ext'])
				->addCacheTag(
					is_array($aFilter['ext'])
						? func_build_cache_keys($aFilter['ext'], 'xtext_file_ext_')
						: 'xtext_file_ext_'.$aFilter['ext']
				)
			;
		}
		if(isset($aFilter['time_min'])){
			$oQ
				->addWhere(
					'time_min',
					'f.file_upload_time>=?d'
				)
				->A($aFilter['time_min'])
			;
		}
		if(isset($aFilter['time_max'])){
			$oQ
				->addWhere(
					'time_max',
					'f.file_upload_time<?d'
				)
				->A($aFilter['time_max'])
			;
		}if(isset($aFilter['size_min'])){
			$oQ
				->addWhere(
					'size_min',
					'f.file_size>=?d'
				)
				->A($aFilter['size_min'])
			;
		}
		if(isset($aFilter['size_max'])){
			$oQ
				->addWhere(
					'size_max',
					'f.file_size<?d'
				)
				->A($aFilter['size_max'])
			;
		}
		if(isset($aFilter['parent_id'])){
			$oQ
				->addWhere(
					'parent_id',
					is_array($aFilter['parent_id'])
						? 'f.file_parent_id in (?a)'
						: 'f.file_parent_id=?d'
				)
				->A($aFilter['parent_id'])
				->addCacheTag(
					is_array($aFilter['parent_id'])
						? func_build_cache_keys($aFilter['parent_id'], 'xtext_file_parent_')
						: 'xtext_file_parent_'.$aFilter['parent_id']
				)
			;
		}
		if(!empty($aFilter['hash'])){
			$oQ
				->addWhere(
					'hash',
					is_array($aFilter['hash'])
						? 'f.file_hash in (?a)'
						: 'f.file_hash=?'
				)
				->A($aFilter['hash'])
				->addCacheTag(
					is_array($aFilter['hash'])
						? func_build_cache_keys($aFilter['hash'], 'xtext_file_hash_')
						: 'xtext_file_hash_'.$aFilter['hash']
				)
			;
		}
		if(!empty($aFilter['path'])){
			$oQ
				->addWhere(
					'path',
					is_array($aFilter['path'])
						? 'f.file_path in (?a)'
						: 'f.file_path=?'
				)
				->A($aFilter['path'])
			;
		}
		if(!empty($aFilter['count'])){
			return $oQ;
		}
		if(!empty($aFilter['order_by'])){
			$oQ->addOrder(
				$aFilter['order_by'],
				!empty($aFilter['order_desc']) ? 'desc' : 'asc'
			);
		}
		if(!empty($aFilter['on_page'])){
			$iOffset = (int) @$aFilter['page']*$aFilter['on_page'];
			$oQ
				->setLimit($aFilter['on_page'])
				->setOffset($iOffset)
			;
		}
		return $oQ;
	}


Как видно, фильтр получается весьма насыщенный. Теперь посмотрим, как он применяется:

	/**
	 * @return PluginXtext_ModuleSql_EntityQuery
	 */
	public function GetCountFilesSql(){
		$oQ = func_xtext_sql()
			->addFromTable(Config::Get('plugin.xtext.table.file'), 'f')
			->addSelect('count(f.file_id)', 'file_count')
			->addCacheTag(
				'xtext_file'
			)
			->setCacheTime(60*60*24)
		;
		return $oQ;
	}


	/**
	 * @return PluginXtext_ModuleSql_EntityQuery
	 */
	public function GetCountFilesByFilterSql($aFilter){
		$oQ = $this->AddFilterGetFilesSql(
			$this->GetCountFilesSql(),
			array('count' => true) + $aFilter
		);
		return $oQ;
	}
	
	
	/**
	 * @return PluginXtext_ModuleSql_EntityQuery
	 */
	public function GetFilesByFilterSql($aFilter){
		$oQ = $this->AddFilterGetFilesSql(
			$this->GetFilesSql(),
			$aFilter
		);
		return $oQ;
	}
	
	public function GetCountFilesByFilter($aFilter){
		if(!($oQ = $this->GetCountFilesByFilterSql($aFilter))){
			return 0;
		}
		return (int) $oQ->Exec('Cell');
	}
	
	public function GetFilesByFilter($aFilter){
		if(!($oQ = $this->GetFilesByFilterSql($aFilter))){
			return array();
		}
		$aFiles = array();
		if($aRows = $oQ->Exec()){
			foreach($aRows as $aRow){
				$aFiles[$aRow['file_id']] = Engine::GetEntity('PluginXtext_File', $aRow);
			}
		}
		return $aFiles;
	}


Да, тут чёрт, может быть и сломит себе не только ногу, но если постараться и разобраться, то выглядит оно весьма вкусно. И вообще, самое место ему в ядре LS :)

Вот такая, понимаешь, загогулина… (ц) Е.Б.Н.

4 комментария

avatar
По примеру не понятна суть ModuleSql_EntityQuery, в чем отличие от Entity?
  • ort
  • 0
avatar
оно и есть instanceof Entity, просто там тонна методов для построения запроса. и конечная ее цель — получить строку SQL-запроса с правильно выбранными аргументами по кускам этого запроса
avatar
Просто судя по коду у тебя построитель в func_xtext_sql(), напиши к какому файлу какой код относится
  • ort
  • 0
avatar
в func_xtext_sql($type) просто вызов Engine::GetEntity('PluginXtext_Sql_Query')->setType($type),
это для краткости вспомогательная функция.

вообще тот модуль есть в брошенной мной админке и примеры использования тоже есть вроде )
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.