/** * Метод возвращает экземпляр класса-хранилища маппингов. * Может быть переопределён в config.ini */ private static final function inst() { if (isset(self::$inst)) { return self::$inst; //---- } /* * Получим название класса */ $class = ConfigIni::mappingStorage(); /* * Класс совпадает с базовым? */ if (__CLASS__ == $class) { return self::$inst = new MappingStorage(); } /* * Нам передан класс, который отличается от SDK */ $classPath = Autoload::inst()->getClassPath($class); if (!PsCheck::isNotEmptyString($classPath)) { return PsUtil::raise('Не удалось найти класс хранилища маппингов [{}]', $class); } /* * Указанный класс должен быть наследником данного */ if (!PsUtil::isInstanceOf($class, __CLASS__)) { return PsUtil::raise('Указанное хранилище маппингов [{}] не является наследником класса [{}]', $class, __CLASS__); } return self::$inst = new $class(); }
/** * Возвращает названия всех классов в директории */ public static function getDirClassNames($__DIR__, $subDir, $parent) { $classes = array(); foreach (DirManager::inst($__DIR__)->getDirContent($subDir, PsConst::EXT_PHP, DirManager::DC_NAMES_NO_EXT) as $name) { if (PsUtil::isInstanceOf($name, $parent)) { $classes[] = $name; } } return $classes; }
/** * @param type $group - группа дискуссий (должна быть уникальна среди всех групп, например - посты или фидбеки) * @param type $subgroup - подгруппа дискуссии (должна быть уникальна в рамках группы) * @param type $unique - уникальный код дискуссии, комбинация группы и подгруппы * @param type $table - таблица с сообщениями * @param type $idColumn - первичный ключ таблицы с сообщениями * @param type $threadIdColumn - столбец с кодом треда (для дискуссий, работающих с тредами) * @param type $themeColumn - столбец с темой (если идёт работа с темой) * @param type $msgClass - клсс, хранящий сообщение. Должен быть наследником DiscussionMsg. * @param type $templatable - признак, можно ли использовать шаблонные сообщения */ public function __construct($group, $subgroup, $table, $idColumn, $msgClass = 'DiscussionMsg', $threadIdColumn = null, $themeColumn = null, $templatable = false, $votable = true) { $this->group = $group; $this->subgroup = $subgroup; $this->unique = $group . ($subgroup ? "-{$subgroup}" : ''); $this->table = $table; $this->idColumn = $idColumn; $this->msgClass = $msgClass; $this->threadIdColumn = $threadIdColumn; $this->themeColumn = $themeColumn; $this->templatable = $templatable; $this->votable = $votable; check_condition(PsUtil::isInstanceOf($msgClass, 'DiscussionMsg'), "Класс '{$msgClass}' не наследует DiscussionMsg"); }
/** * Метод возвращает экземпляр класса-плагина Smarty. * Для переопределения этого класса, на уровне проектного config.ini * должен быть задан другой класс. * * Это позволит использовать стандартизованный метод подключения плагинов */ public static final function inst() { if (isset(self::$inst)) { return self::$inst; //---- } /* * Получим название класса */ $class = ConfigIni::smartyPlugin(); /* * Класс подключения библиотек совпадает с базовым */ if (__CLASS__ == $class) { return self::$inst = new PSSmartyPlugin(); } /* * Нам передан класс, который отличается от SDK */ $classPath = Autoload::inst()->getClassPath($class); if (!PsCheck::isNotEmptyString($classPath)) { return PsUtil::raise('Не удалось найти класс плагинов Smarty [{}]', $class); } /* * Указанный класс должен быть наследником данного */ if (!PsUtil::isInstanceOf($class, __CLASS__)) { return PsUtil::raise('Указанный плагин Smarty [{}] не является наследником класса [{}]', $class, __CLASS__); } return self::$inst = new $class(); }
/** * @covers PsUtil::isInstanceOf */ public function testIsInstanceOf() { $this->assertTrue(PsUtil::isInstanceOf(new ClassA(), 'InterfaceA')); $this->assertTrue(PsUtil::isInstanceOf(new ClassA(), new ClassA())); $this->assertTrue(PsUtil::isInstanceOf(new ClassA(), ClassA::get__CLASS__())); $this->assertFalse(PsUtil::isInstanceOf(new ClassA(), 'InterfaceB')); $this->assertFalse(PsUtil::isInstanceOf(new ClassA(), __CLASS__)); $this->assertTrue(PsUtil::isInstanceOf($this, __CLASS__)); }
/** * Определим функцию, которая выполнит все действия - не будем лишними переменными засорять глобальное пространство */ function psExecuteAjaxAction() { /* * Название действия должно быть в переменной запроса. Оно же - название класса, который будет выполнен. * Группа действия должны быть не обязательна, при определении действия группа нужна обязательно. */ $actionName = RequestArrayAdapter::inst()->str(AJAX_ACTION_PARAM); $actionGroup = RequestArrayAdapter::inst()->str(AJAX_ACTION_GROUP_PARAM, 'client'); if (!PsCheck::notEmptyString($actionName) || !PsCheck::notEmptyString($actionGroup)) { return json_error('Не передан код действия или его группа'); //--- } /* * Экземпляр класса действия - должен быть наследником AbstractAjaxAction */ $action = null; /* * Поищем в проектных действиях, они для нас имеют больший приоритет */ foreach (ConfigIni::ajaxActionsAbs($actionGroup) as $dirAbsPath) { $classPath = file_path($dirAbsPath, $actionName, PsConst::EXT_PHP); if (is_file($classPath)) { /* * Нашли файл. Загрузим и проверим, является ли он наследником AbstractAjaxAction */ require_once $classPath; if (!PsUtil::isInstanceOf($actionName, AbstractAjaxAction::getClassName())) { continue; //--- } $action = new $actionName(); break; //--- } } /* * Проверим, существует ли действие. * Для безопасности не будем писать детали обработки. */ if (!$action || !$action instanceof AbstractAjaxAction) { return json_error('Действие не опеределено'); //--- } /* * Выполняем */ $result = null; try { $result = $action->execute(); } catch (Exception $e) { $result = $e->getMessage(); } /* * Проверим результат */ if ($result instanceof AjaxSuccess) { json_success($result->getJsParams()); } else { json_error($result ? $result : 'Ошибка выполнения действия'); } }
/** * Метод вызывается для выполнения периодических задач cron * * @return type */ public function execute() { if ($this->called) { return $this->executed; //--- } $this->called = true; $LOGGER = PsLogger::inst(__CLASS__); $LOGGER->info('Executing {}', __CLASS__); /* * Получим список классов, которые нужно выполнить */ $processes = ConfigIni::cronProcesses(); if (empty($processes)) { $LOGGER->info('No cron processes configured, fast return...'); return $this->executed; //--- } $processes = array_unique($processes); $LOGGER->info('Configured processes: {}', array_to_string($processes)); foreach ($processes as $class) { if (!PsUtil::isInstanceOf($class, 'PsCronProcess')) { PsUtil::raise("Class {$class} cannot be executed as cron process, it should be instance of PsCronProcess"); } } $locked = PsLock::lock(__CLASS__, false); $LOGGER->info('Lock accured ? {}', var_export($locked, true)); if (!$locked) { return $this->executed; //--- } $LOCKFILE = DirManager::autoNoDel(DirManager::DIR_SERVICE)->getDirItem(null, __CLASS__, PsConst::EXT_LOCK); $LOCKFILE_LIFETIME = $LOCKFILE->getFileLifetime(); $MAX_LIFETIME = 5 * 60; $NED_PROCESS = $LOCKFILE_LIFETIME === null || $LOCKFILE_LIFETIME > $MAX_LIFETIME; $LOGGER->info("Lock file {}: {}", $LOCKFILE_LIFETIME === null ? 'NOT EXISTS' : 'EXISTS', $LOCKFILE->getRelPath()); if ($LOCKFILE_LIFETIME !== null) { $LOGGER->info('Last modified: {} seconds ago. Max process delay: {} seconds.', $LOCKFILE_LIFETIME, $MAX_LIFETIME); // } if (!$NED_PROCESS) { $LOGGER->info('Skip execution.'); //Отпустим лок PsLock::unlock(); //Выходим return $this->executed; //--- } //Обновим время последнего выполнения $LOCKFILE->touch(); //Отпустим лок, так как внутри он может потребоваться для выполнения других действий, например для перестройки спрайтов PsLock::unlock(); $LOGGER->info(); $LOGGER->info('External process execution started...'); //Запускаем режим неограниченного выполнения PsUtil::startUnlimitedMode(); //Начинаем выполнение $this->executed = true; //Создаём профайлер $PROFILER = PsProfiler::inst(__CLASS__); //Создадим конфиг выполнения процесса $config = new PsCronProcessConfig(); //Пробегаемся по процессам и выполняем. При первой ошибке - выходим. foreach ($processes as $class) { $LOGGER->info('Executing cron process {}', $class); $PROFILER->start($class); try { $inst = new $class(); $inst->onCron($config); $secundomer = $PROFILER->stop(); $LOGGER->info(" > Cron process '{}' executed in {} seconds", $class, $secundomer->getTotalTime()); } catch (Exception $ex) { $PROFILER->stop(); $LOGGER->info(" > Cron process '{}' execution error: '{}'", $class, $ex->getMessage()); } } $LOGGER->info('Removing cron lock file.'); $LOCKFILE->remove(); return $this->executed; }
/** @return FileUploader */ public static final function inst($classPrefix = null) { $class = $classPrefix ? ensure_ends_with($classPrefix, 'Uploader') : get_called_class(); check_condition(PsUtil::isInstanceOf($class, __CLASS__), "Class {$class} is not instance of " . __CLASS__); return array_key_exists($class, self::$insts) ? self::$insts[$class] : (self::$insts[$class] = new $class()); }
/** * Метод возвращает экземпляр класса-хранилища экземпляров фолдинов. * Для переопределения этого класса, на уровне проектного config.ini * должен быть задан другой класс. * * @return FoldedStorageInsts */ protected static final function inst() { if (isset(self::$inst)) { return self::$inst; //---- } /* * Получим название класса */ $class = FoldingsIni::foldingsStore(); /* * Класс совпадает с базовым */ if (__CLASS__ == $class) { return self::$inst = new FoldedStorageInsts(); } /* * Нам передан класс, который отличается от SDK */ $classPath = Autoload::inst()->getClassPath($class); if (!PsCheck::isNotEmptyString($classPath)) { return PsUtil::raise('Не удалось найти класс регистрации экземпляров фолдингов [{}]', $class); } /* * Указанный класс должен быть наследником данного */ if (!PsUtil::isInstanceOf($class, __CLASS__)) { return PsUtil::raise('Указанный класс регистрации экземпляров фолдингов [{}] не является наследником класса [{}]', $class, __CLASS__); } return self::$inst = new $class(); }
/** * Метод возвращает экземпляр класса, подключающего библиотеки. * Для переопределения этого класса, на уровне проектного config.ini * должен быть задан другой класс, отвечающий за подключение библиотек. * * Это позволит: * 1. Использовать стандартизованный метод подключения внешних библиотек * 2. Переопределить подключение библиотек из SDK */ public static final function inst() { if (isset(self::$inst)) { return self::$inst; //---- } /* * Получим название класса, отвечающего за подключение библиотек */ $class = ConfigIni::libsIncluder(); /* * Подготовим директории */ $SDK_LIB_DIR = DirManager::inst(PS_DIR_INCLUDES, DirManager::DIR_LIB)->absDirPath(); $PROJ_LIB_DIR = DirManager::inst(PS_DIR_ADDON, DirManager::DIR_LIB)->absDirPath(); /* * Класс подключения библиотек совпадает с базовым */ if (__CLASS__ == $class) { self::$inst = new PsLibs(); self::$inst->SDK_LIB_DIR = $SDK_LIB_DIR; self::$inst->LOGGER = PsLogger::inst($class); self::$inst->LOGGER->info('Libs includer SDK: [{}]', __FILE__); self::$inst->LOGGER->info('Libs directory SDK: [{}]', $SDK_LIB_DIR); return self::$inst; //--- } /* * Нам передан класс, который отличается от SDK */ $classPath = Autoload::inst()->getClassPath($class); if (!PsCheck::isNotEmptyString($classPath)) { return PsUtil::raise('Не удалось найти класс загрузчика библиотек [{}]', $class); } /* * Указанный класс должен быть наследником данного */ if (!PsUtil::isInstanceOf($class, __CLASS__)) { return PsUtil::raise('Указанный загрузчик библиотек [{}] не является наследником класса [{}]', $class, __CLASS__); } self::$inst = new $class(); self::$inst->SDK_LIB_DIR = $SDK_LIB_DIR; self::$inst->PROJ_LIB_DIR = $PROJ_LIB_DIR; self::$inst->LOGGER = PsLogger::inst($class); self::$inst->LOGGER->info('Libs includer CUSTOM: [{}]', $classPath); self::$inst->LOGGER->info('Libs directory SDK: [{}]', $SDK_LIB_DIR); self::$inst->LOGGER->info('Libs directory CUSTOM: [{}]', $PROJ_LIB_DIR); return self::$inst; //--- }
public function fetchTplImpl($ident, $smParams = null, $returnType = self::FETCH_RETURN_CONTENT, $addResources = false, $cacheId = null) { $this->assertHasAccess($ident); $logMsg = null; if ($this->LOGGER->isEnabled()) { $rqNum = ++self::$FETCH_REQUEST_CNT; $logMsg = "#{$rqNum} Smarty params count: " . count(to_array($smParams)) . ", type: {$returnType}, resources: " . var_export($addResources, true) . ", " . ($cacheId ? "cache id: [{$cacheId}]" : 'nocache'); $this->LOGGER->info("Tpl fetching requested for entity [{$ident}]. {$logMsg}"); FoldedResourcesManager::onEntityAction(FoldedResourcesManager::ACTION_ENTITY_FETCH_REQUESTD, $this, $ident, $logMsg); } $entity = $this->getFoldedEntity($ident); //Сразу установим зависимость от текущей сущности FoldedContextWatcher::getInstance()->setDependsOnEntity($entity); $CTXT = $this->getFoldedContext(); $PCLASS = $CTXT->tplFetchParamsClass(); $PCLASS_BASE = FoldedTplFetchPrams::getClassName(); check_condition(PsUtil::isInstanceOf($PCLASS, $PCLASS_BASE), "Класс [{$PCLASS}] для хранения данных контекста {$CTXT} должен быть подклассом {$PCLASS_BASE}"); //Если мы не возвращаем содержимое, то в любом случае ресурсы добавлять не к чему $addResources = $addResources && !in_array($returnType, array(self::FETCH_RETURN_PARAMS, self::FETCH_RETURN_PARAMS_OB)); $keysRequired = PsUtil::getClassConsts($PCLASS, 'PARAM_'); $keysRequiredParams = array_diff($keysRequired, array(FoldedTplFetchPrams::PARAM_CONTENT)); $PARAMS = null; $PARAMS_KEY = null; $CONTENT = null; $CONTENT_KEY = null; $RETURN_KEY = null; if ($cacheId) { $cacheId = ensure_wrapped_with($cacheId, '[', ']') . '[' . PsDefines::getReplaceFormulesType() . ']'; $RETURN_KEY = $cacheId . '-' . $returnType; if (array_key_exists($ident, $this->FETCH_RETURNS)) { if (array_key_exists($RETURN_KEY, $this->FETCH_RETURNS[$ident])) { return $this->FETCH_RETURNS[$ident][$RETURN_KEY]; } } else { $this->FETCH_RETURNS[$ident] = array(); } $PARAMS_KEY = empty($keysRequiredParams) ? null : $cacheId . '-params'; $CONTENT_KEY = $cacheId . '-content'; switch ($returnType) { case self::FETCH_RETURN_FULL: case self::FETCH_RETURN_FULL_OB: $CONTENT = $this->getFromFoldedCache($ident, $CONTENT_KEY); $PARAMS = $PARAMS_KEY ? $this->getFromFoldedCache($ident, $PARAMS_KEY, $keysRequiredParams) : array(); if ($CONTENT && is_array($PARAMS)) { $CONTENT = $addResources ? $this->getResourcesLinks($ident, $CONTENT) : $CONTENT; $PARAMS[FoldedTplFetchPrams::PARAM_CONTENT] = $CONTENT; switch ($returnType) { case self::FETCH_RETURN_FULL: return $this->FETCH_RETURNS[$ident][$RETURN_KEY] = $PARAMS; case self::FETCH_RETURN_FULL_OB: return $this->FETCH_RETURNS[$ident][$RETURN_KEY] = new $PCLASS($PARAMS); default: raise_error("Unprocessed fetch return type [{$returnType}]."); } } break; case self::FETCH_RETURN_CONTENT: $CONTENT = $this->getFromFoldedCache($ident, $CONTENT_KEY); if ($CONTENT) { $CONTENT = $addResources ? $this->getResourcesLinks($ident, $CONTENT) : $CONTENT; return $this->FETCH_RETURNS[$ident][$RETURN_KEY] = $CONTENT; } break; case self::FETCH_RETURN_PARAMS: case self::FETCH_RETURN_PARAMS_OB: $PARAMS = $PARAMS_KEY ? $this->getFromFoldedCache($ident, $PARAMS_KEY, $keysRequiredParams) : array(); if (is_array($PARAMS)) { switch ($returnType) { case self::FETCH_RETURN_PARAMS: return $this->FETCH_RETURNS[$ident][$RETURN_KEY] = $PARAMS; case self::FETCH_RETURN_PARAMS_OB: return $this->FETCH_RETURNS[$ident][$RETURN_KEY] = new $PCLASS($PARAMS); default: raise_error("Unprocessed fetch return type [{$returnType}]."); } } break; } } $settedNow = false; if (!$entity->equalTo(FoldedContextWatcher::getInstance()->getFoldedEntity())) { $CTXT->setContextWithFoldedEntity($entity); $settedNow = true; } try { $CONTENT = $this->getTpl($ident, $smParams)->fetch(); $entityNow = FoldedContextWatcher::getInstance()->getFoldedEntity(); check_condition($entity->equalTo($entityNow), "After tpl fetching folded entity [{$entity}] chenged to [{$entityNow}]"); $PARAMS_FULL = $CTXT->finalizeTplContent($CONTENT); check_condition(is_array($PARAMS_FULL), "After [{$entity}] tpl finalisation not array is returned"); $keysReturned = array_keys($PARAMS_FULL); if (count(array_diff($keysReturned, $keysRequired)) || count(array_diff($keysRequired, $keysReturned))) { raise_error("After [{$entity}] tpl finalisation required keys: " . array_to_string($keysRequired) . '], returned keys: [' . array_to_string($keysReturned) . ']'); } if ($this->LOGGER->isEnabled()) { $this->LOGGER->info("Tpl fetching actually done for entity [{$ident}]. {$logMsg}"); FoldedResourcesManager::onEntityAction(FoldedResourcesManager::ACTION_ENTITY_FETCH_DONE, $this, $ident, $logMsg); } } catch (Exception $e) { /* * Произошла ошибка! * * Если мы устанавливали контенст и он не поменялся после завершения фетчинга (если поменялся, это ошибка), то нужно его обязательно завершить. * Если контекст был установлен во внешнем блоке, то этот блок должен позаботиться о сбросе контекста. * * Далее от нас требуется только пробросить ошибку наверх. */ if ($settedNow && $entity->equalTo(FoldedContextWatcher::getInstance()->getFoldedEntity())) { $CTXT->dropContext(); } throw $e; } $CONTENT = $PARAMS_FULL[FoldedTplFetchPrams::PARAM_CONTENT]; $PARAMS = $PARAMS_FULL; unset($PARAMS[FoldedTplFetchPrams::PARAM_CONTENT]); if ($PARAMS_KEY) { $this->saveToFoldedCache($PARAMS, $ident, $PARAMS_KEY); } if ($CONTENT_KEY) { $this->saveToFoldedCache($CONTENT, $ident, $CONTENT_KEY); } if ($settedNow) { $CTXT->dropContext(); } if ($addResources) { $CONTENT = $this->getResourcesLinks($ident, $CONTENT); $PARAMS_FULL[FoldedTplFetchPrams::PARAM_CONTENT] = $CONTENT; } switch ($returnType) { case self::FETCH_RETURN_FULL: return $RETURN_KEY ? $this->FETCH_RETURNS[$ident][$RETURN_KEY] = $PARAMS_FULL : $PARAMS_FULL; case self::FETCH_RETURN_FULL_OB: return $RETURN_KEY ? $this->FETCH_RETURNS[$ident][$RETURN_KEY] = new $PCLASS($PARAMS_FULL) : new $PCLASS($PARAMS_FULL); case self::FETCH_RETURN_CONTENT: return $RETURN_KEY ? $this->FETCH_RETURNS[$ident][$RETURN_KEY] = $CONTENT : $CONTENT; case self::FETCH_RETURN_PARAMS: return $RETURN_KEY ? $this->FETCH_RETURNS[$ident][$RETURN_KEY] = $PARAMS : $PARAMS; case self::FETCH_RETURN_PARAMS_OB: return $RETURN_KEY ? $this->FETCH_RETURNS[$ident][$RETURN_KEY] = new $PCLASS($PARAMS) : new $PCLASS($PARAMS); } raise_error("Unknown fetch return type [{$returnType}]."); }
/** * Конструктор класса для работы с кешем. * Кеширование идёт в два этапа: * 1. Кеширование на уровне класса для максимально быстрого доступа * 2. Кеширование в долгосрочном хранилище, которое реализуется отдельным классом - "движком" кеширования * * Движок кеширования должен быть задан на уровне config.ini * * @return PSCache */ protected function __construct() { $this->LOGGER = PsLogger::inst(__CLASS__); /* * Получим название класса "движка" кеширования */ $class = ConfigIni::cacheEngine(); /* * Проверим наличие класса */ $classPath = Autoload::inst()->getClassPath($class); if (!PsCheck::isNotEmptyString($classPath)) { return PsUtil::raise('Не удалось найти класс для кеширования [{}]', $class); } /* * Правильный ли класс указан? */ if (!PsUtil::isInstanceOf($class, 'PSCacheEngine')) { return PsUtil::raise('Указанный класс кеширования [{}] не является наследником класса [{}]', $class, 'PSCacheEngine'); } $this->LOGGER->info('Используем движок кеширования: {}', $class); $this->CACHE_ENGINE = new $class(); }
/** * В конструкторе пробежимся по всем хранилищам и соберём все фолдинги */ protected function __construct() { $this->LOGGER = PsLogger::inst(__CLASS__); $this->PROFILER = PsProfiler::inst(__CLASS__); /* * Инициалилизируем коллекцию */ $this->PROVIDERS = array(); /* * Собираем полный список доступных хранилищ и менеджеров фолдингов в них */ $providerClasses = ConfigIni::getPropCheckType(ConfigIni::GROUP_FOLDINGS, 'providers', array(PsConst::PHP_TYPE_ARRAY, PsConst::PHP_TYPE_NULL)); $providerClasses = to_array($providerClasses); $providerClasses[] = FoldingsProviderSdk::calledClass(); $this->LOGGER->info('Providers: {}', array_to_string($providerClasses)); foreach ($providerClasses as $provider) { if (in_array($provider, $this->PROVIDERS)) { $this->LOGGER->info('[-] {} - {}', $provider, 'is already registered'); continue; //--- } if (!class_exists($provider)) { $this->LOGGER->info('[-] {} - {}', $provider, 'is not included'); continue; //--- } if (!PsUtil::isInstanceOf($provider, FoldingsProviderAbstract::calledClass())) { $this->LOGGER->info('[-] {} - {}', $provider, 'is not instance of ' . FoldingsProviderAbstract::calledClass()); continue; //--- } $this->LOGGER->info('[+] {}', $provider); $this->PROVIDERS[] = $provider; /* * Для каждого хранилища загружаем список входящих в него фолдингов */ $this->PROFILER->start($provider . '::list'); /* @var $folding FoldedResources */ foreach ($provider::listFoldings() as $folding) { $funique = $folding->getUnique(); if (array_key_exists($funique, $this->UNIQUE_2_PROVIDER)) { raise_error(PsStrings::replaceWithBraced('Folding {} is provided by: {}, {}', $funique, $this->UNIQUE_2_PROVIDER[$funique], $provider)); } $this->LOGGER->info(' [>] {}', $funique); $this->UNIQUE_2_PROVIDER[$funique] = $provider; $this->UNIQUE_2_FOLDING[$funique] = $folding; } $this->PROFILER->stop(); } }