217 lines
10 KiB
Plaintext
217 lines
10 KiB
Plaintext
<!--- *** Refactoring required --->
|
||
<!--- v0.3
|
||
2017-08-30
|
||
Настройки вынесены в таблицу usr_settings в связи с миграцией репозитория пользователей в мастер справочники
|
||
Заодно cfqueryparam
|
||
|
||
модуль должен инкапсулировать работу с настройками
|
||
- хранение в сессии и в БД, синхронность с базой
|
||
- чтение и модификацию
|
||
- очистку(сброс)
|
||
При этом модуль ничего не должен знать о структуре самих настроек (фильтрах, сортировках и т.д.)
|
||
|
||
Жизненный цикл контейнера settings
|
||
1. Живет в сессии
|
||
2. Если в сессии нет settings - вычитывается из БД
|
||
3. Если и в БД нет - инициализируется и сразу сохраняется в БД (пустой)
|
||
4. При модификации синхронизируется с базой (пишет в нее) - нужно либо снаружи вызывать сохранение, либо инкапсулировать запись
|
||
(инкапсулировать запись - сильно усложнит код)
|
||
5. Для работы выдает КОПИЮ данных, соответственно, при сохранении должен ее синхронизировать???
|
||
Пока решили, что будем выдавать ссылку на объект в сессии, при этом нужно блок кода синхронизировать CFLOCK
|
||
При этом 2 проблемы:
|
||
- некрасивый код (лок на сессию руками, связь совершенно неявная)
|
||
- избыточная блокировка
|
||
- избыточное сохранение (можно было бы проверять на изменение)
|
||
чтобы действовать без блокировки, можно экспортировать объект в безопасный scope - переменные или реквест
|
||
Альтернативный синтаксис вызова
|
||
<m:settings key="page_name" output="settings"><!--- implicit get --->
|
||
<m:sortparam sort=#settings.sort# fieldcount=9 sortlength=2/><!--- any modification --->
|
||
</m:settings><!--- implicit sync --->
|
||
|
||
|
||
Разрешение конфликта версий при обновлении софта
|
||
Конфликт версий: код требует параметр, отсутствующий в сохраненной версии. Например, одна версия фильтра сохранила свои настройки,
|
||
а другая, более новая, пытается прочесть несуществующий ключ.
|
||
Другой вариант - несуществующее поле (убранное в очередной версии) - этот конфликт проявится только при запросе к БД.
|
||
Варианты решения
|
||
- создание параметра с дефолтными значениями при запросе (во все запросы добавить значения по умолчанию). Недостаток - возможно создание помойки
|
||
- контроль номера версии. Выглядит сложным в сопровождении
|
||
*** Возможно, это была плохая идея - разрешить доступ по длинному ключу. Когда доставали и записывали целиком объект, было меньше возможностей для ошибок
|
||
--->
|
||
|
||
<!---*** обработка вызова get-sync c закрывающим тегом --->
|
||
<cfparam name="ATTRIBUTES.action" type="string" default=""/>
|
||
<cfparam name="obj" type="struct" default=#structNew()#/>
|
||
<cfparam name="doSyncOnClose" type="boolean" default="No"/>
|
||
|
||
<cfif thisTag.executionMode IS "start">
|
||
<cfif ATTRIBUTES.action IS "">
|
||
<cfset ATTRIBUTES.action="get"/>
|
||
<cfset doSyncOnClose="Yes"/>
|
||
</cfif>
|
||
<cfelse>
|
||
<cfif doSyncOnClose>
|
||
<cfset ATTRIBUTES.action="sync"/>
|
||
<cfelse>
|
||
<cfexit method="exittag"/>
|
||
</cfif>
|
||
</cfif>
|
||
|
||
<cfset useDB="false"/><!--- --->
|
||
|
||
<cfswitch expression=#lCase(ATTRIBUTES.action)#>
|
||
|
||
<cfcase value="create"> <!--- конструктор --->
|
||
<cflock scope="SESSION" type="EXCLUSIVE" timeout="3">
|
||
<cfset session.settings = retrieveSettings()>
|
||
</cflock>
|
||
</cfcase>
|
||
|
||
<cfcase value="sync">
|
||
<!--- Синхронизировать измененный объект (с сессией и персистенсом) --->
|
||
<cfparam name="ATTRIBUTES.key" type="string">
|
||
<cfif doSyncOnClose>
|
||
<cfif thisTag.executionMode IS "end">
|
||
<cfset obj=StructFind(CALLER, "#ATTRIBUTES.output#")/>
|
||
</cfif>
|
||
<cfelse>
|
||
<cfparam name="ATTRIBUTES.obj" type="struct">
|
||
<cfset obj=#ATTRIBUTES.obj#/>
|
||
</cfif>
|
||
<!---<cfdump var=#obj#/>--->
|
||
|
||
<cflock scope="SESSION" type="EXCLUSIVE" timeout="3">
|
||
<!--- помещаем объект на свое место в структуре --->
|
||
<cfset container=#session.settings#>
|
||
<cfset cnt = listLen("#ATTRIBUTES.key#", ".")>
|
||
<cfloop index="i" from=1 to=#cnt#>
|
||
<cfset item = listGetAt(#ATTRIBUTES.key#, i, ".")>
|
||
<cfif i EQ cnt><!--- last element --->
|
||
<cfset structInsert(#container#, #item#, #obj#, "YES")>
|
||
<cfelse>
|
||
<!--- попытка перебдеть--->
|
||
<cfif NOT structKeyExists(#container#,#item#)>
|
||
<cfset structInsert(#container#,#item#,#structNew()#)>
|
||
</cfif>
|
||
<cfset container=structFind(container,item)>
|
||
</cfif>
|
||
</cfloop>
|
||
</cflock>
|
||
<!--- сохраняем настройки в персистенс --->
|
||
<cfset saveSettings(container)>
|
||
</cfcase>
|
||
|
||
|
||
<cfcase value="reset"><!--- *** надо было назвать reset_all или как-то так --->
|
||
<!--- Сбросить все пользовательские настройки --->
|
||
<cfset session.settings=structNew()>
|
||
<cfset saveSettings(session.settings)>
|
||
</cfcase>
|
||
|
||
<cfcase value="clear">
|
||
<!--- Очистить секцию пользовательских настроек (sort или filter)--->
|
||
<cfparam name="ATTRIBUTES.key" type="string">
|
||
<cfset targetFullName = "session.settings.#ATTRIBUTES.key#">
|
||
<cfset objName = listGetAt(ATTRIBUTES.key,1,".")>
|
||
<cfif isDefined(targetFullName)>
|
||
<cflock scope="SESSION" type="EXCLUSIVE" timeout="3">
|
||
<cfset evaluate("structClear(#targetFullName#)")>
|
||
<cfset obj = evaluate(#targetFullName#)>
|
||
<cfmodule template="settings.cfm" action="sync" key=#ATTRIBUTES.key# obj=#obj#><!--- синхронизация при очистке используется ---><!--- хорошо ли это? А вообще надо?--->
|
||
</cflock>
|
||
</cfif>
|
||
</cfcase>
|
||
|
||
<cfcase value="get">
|
||
<!---
|
||
Получить объект или секцию
|
||
Обязательный параметр key - имя ключа в структуре, начинающееся с имени объекта, через точку
|
||
В параметре output имя переменной, принимающей результат
|
||
--->
|
||
<cfparam name="ATTRIBUTES.key" type="string">
|
||
<cfparam name="ATTRIBUTES.output" type="string">
|
||
|
||
<cfif NOT isDefined("session.settings")><!--- *** container--->
|
||
<cfmodule template="settings.cfm" action="create"/>
|
||
</cfif>
|
||
|
||
<cfset targetFullName = "session.settings.#ATTRIBUTES.key#">
|
||
<cfset objName = listGetAt(ATTRIBUTES.key, 1, ".")>
|
||
<cfif (NOT structKeyExists(#session.settings#, objName))
|
||
OR structIsEmpty(structFind(#session.settings#, objName))>
|
||
<cfmodule template="init_settings.cfm" container=#session.settings# objName=#objName#/>
|
||
<!--- несколько неуклюже, лучше просто возвращать объект и потом класть в сессию --->
|
||
</cfif>
|
||
<cftry>
|
||
<cfset obj=evaluate(targetFullName)>
|
||
<cfcatch type="ANY">
|
||
<cfmodule template="init_settings.cfm" container=#session.settings# objName=#objName#/><!---*** грубо --->
|
||
<cfset obj=evaluate(targetFullName)>
|
||
</cfcatch>
|
||
</cftry>
|
||
<cfset setVariable("CALLER.#ATTRIBUTES.output#", #duplicate(obj)#)>
|
||
</cfcase>
|
||
<cfdefaultcase>
|
||
<cfthrow message="Unsupported action = #ATTRIBUTES.action# in settings.cfm">
|
||
</cfdefaultcase>
|
||
</cfswitch>
|
||
<!---***debug <cfoutput>#ATTRIBUTES.action# #thisTag.executionMode# #thisTag.hasendtag#</cfoutput>--->
|
||
|
||
|
||
|
||
<!--- --------------------------------------------------------------------------------- --->
|
||
<!--- private methods --->
|
||
<cffunction name="retrieveSettings" access="private">
|
||
<cfif useDB><!--- можно перегрузить методы для разных провайдеров персистенса, но пока смысла в этом не видно, код достаточно примитивен и управляем --->
|
||
<cfquery name="qGet" datasource="#request.DS#">
|
||
select settings from usr where usr_id='#request.usr_id#'
|
||
</cfquery>
|
||
<cfif len(qGet.settings) GT 0>
|
||
<cftry>
|
||
<CFWDDX ACTION='wddx2cfml' input=#qGet.settings# output='settings'/>
|
||
<cfreturn #settings#/>
|
||
<cfcatch type="Any">Err:
|
||
<cfoutput>#cfcatch.message#</cfoutput>
|
||
<cfabort/>
|
||
</cfcatch>
|
||
</cftry>
|
||
</cfif>
|
||
</cfif>
|
||
<cfreturn #structNew()#/>
|
||
</cffunction>
|
||
|
||
<cffunction name="saveSettings" access="private">
|
||
<cfargument name=settings>
|
||
|
||
<cfif !isNumeric(request.usr_id)
|
||
OR !(request.usr_id GT 0)
|
||
OR request.usr_id EQ request.ANONYMOUS_USR_ID
|
||
OR request.usr_id EQ request.UNDEFINED_USR_ID>
|
||
<cfreturn />
|
||
</cfif>
|
||
|
||
<cfif useDB>
|
||
<cfquery name="qCheckExistence" datasource="#request.DS#">
|
||
select count(*) as cnt from usr_serrings where usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value='#request.usr_id#'/>
|
||
</cfquery>
|
||
|
||
<CFWDDX ACTION='cfml2wddx' input=#session.settings# output='strSettings'/>
|
||
|
||
<cfif qCheckExistence.cnt EQ 0>
|
||
<cfquery name="qSave" datasource="#request.DS#">
|
||
insert int usr_settings (usr_id, settings)
|
||
values(<cfqueryparam cfsqltype="cf_sql_integer" value='#request.usr_id#'/>, <cfqueryparam cfsqltype="cf_sql_varchar" value='#strSettings#'/>)
|
||
</cfquery>
|
||
<cfelse>
|
||
<cfquery name="qSave" datasource="#request.DS#">
|
||
update usr_settings
|
||
set settings=<cfqueryparam cfsqltype="cf_sql_varchar" value='#strSettings#'/>
|
||
where usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value='#request.usr_id#'/>
|
||
</cfquery>
|
||
</cfif>
|
||
<cfelse>
|
||
<!--- do nothing --->
|
||
</cfif>
|
||
</cffunction>
|
||
|