Forked from
Stud.IP / Stud.IP
3318 commits behind the upstream repository.
-
Closes #1230 Merge request studip/studip!741
Closes #1230 Merge request studip/studip!741
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ModuleManagementModel.php 33.38 KiB
<?php
/**
* ModuleManagementModel.php
* Parent class of all MVV-Models
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* @author Peter Thienel <thienel@data-quest.de>
* @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
* @category Stud.IP
* @since 3.5
*/
require_once 'config/mvv_config.php';
abstract class ModuleManagementModel extends SimpleORMap implements ModuleManagementInterface
{
/**
* Usable as option for ModuleManagementModel::getDisplayName().
* Use the deafault display options for this object.
*/
const DISPLAY_DEFAULT = 1;
/**
* Usable as option for ModuleManagementModel::getDisplayName().
* Displays semesters of the validity period if available for this object.
*/
const DISPLAY_SEMESTER = 2;
/**
* Usable as option for ModuleManagementModel::getDisplayName().
* Displays the code (usually a unique identifier) if available for this object.
*/
const DISPLAY_CODE = 4;
/**
* Usable as option for ModuleManagementModel::getDisplayName().
* Displays the name of the faculty if available for this object.
*/
const DISPLAY_FACULTY = 8;
/**
* Usable as option for ModuleManagementModel::getDisplayName().
* Displays the name of the Fach (subject of study) if available for this object.
*/
const DISPLAY_FACH = 16;
/**
* Usable as option for ModuleManagementModel::getDisplayName().
* Displays the name of the Studiengangteil if available for this object.
*/
const DISPLAY_STGTEIL = 32;
/**
* Usable as option ModuleManagementModel::getDisplayName().
* Displays the name of the Abschluss if available for this object.
*/
const DISPLAY_ABSCHLUSS = 64;
/**
* Usable as option ModuleManagementModel::getDisplayName().
* Displays the name of the Abschluss-Kategorie
* if available for this object.
*/
const DISPLAY_KATEGORIE = 128;
protected static $filter_params = [];
protected $is_dirty = false;
private static $language = null;
protected static $perm_object = null;
public $object_real_name = '';
protected static $object_cache = [];
/**
* Common configuration for all module management models. This will ensure
* that the caches are flushed whenever an object changes or is removed.
*
* @param array $config Configuration from derived class
*/
protected static function configure($config = [])
{
$config['registered_callbacks']['after_store'][] = function () {
self::clearCache();
};
$config['registered_callbacks']['after_delete'][] = function () {
self::clearCache();
};
parent::configure($config);
}
/**
* Returns a collection of a MVV object type found by search term optionally
* filtered.
*
* @see ModuleManagementModel::getFilterSql()
* @param string $search_term The term to search for.
* @param array $filter Filter parameters as key value pairs.
* @return SimpleORMapCollection A collection of "self" objects.
*/
public static function findBySearchTerm($search_term, $filter = null)
{
return new SimpleORMapCollection();
}
/**
* Returns an array of all objects of "self" type.
*
* @return array An array of "self" objects.
*/
public static function getAll()
{
return parent::findBySQL('1');
}
/**
* Returns an object by given id or a new object.
*
* @param string $id The id of the object.
* @return ModuleManagementModel An object of "self" type.
*/
public static function get($id = null)
{
return static::findCached($id)
?? new static($id);
}
/**
* Returns an object by given id with all relations and additional fields.
*
* @param string $id The id of the object.
* @return ModuleManagementModel
*/
public static function getEnriched($id)
{
return parent::find($id);
}
/**
* Verifies whether the given user has sufficient rights to create, modify
* or delete this object and throws an exception if not.
*
* @param string $user_id The user's id.
* @return boolean True if rights are sufficient
* @throws Exception if rights are not sufficient.
*/
public function verifyPermission($user_id = null)
{
$user_id = $user_id ?: $GLOBALS['user']->id;
$perm = MvvPerm::get($this);
// PERM_CREATE means a permission to store a new one or delete one
if ($this->isNew() || $this->isDeleted()) {
if (!$perm->haveObjectPerm(MvvPerm::PERM_CREATE, $user_id)) {
throw new Exception(sprintf(
'Permission denied! The user is not allowed to '
. 'create/delete an object of type %s.', static::class));
}
} else {
if (!$perm->haveObjectPerm(MvvPerm::PERM_WRITE, $user_id)) {
throw new Exception(sprintf(
'Permission denied! The user is not allowed to store an '
. 'object of type %s', static::class));
}
}
// check the permissions for every single db field except primary keys
if ($this->isNew()) {
$fields = array_diff(array_keys($this->db_fields),
array_values($this->pk));
} else {
$fields = array_keys($this->db_fields);
}
foreach ($fields as $field) {
if ($this->isFieldDirty($field)
&& !$perm->haveFieldPerm($field, MvvPerm::PERM_WRITE, $user_id)) {
throw new Exception(sprintf(
'Permission denied! The user is not allowed to change '
. 'value of field %s.%s.', static::class, $field));
}
}
// check the permissions for every single relation
foreach (array_keys($this->relations) as $relation) {
$options = $this->getRelationOptions($relation);
if ((isset($options['on_store']) || isset($options['on_delete'])) &&
($options['type'] === 'has_one' ||
$options['type'] === 'has_many' ||
$options['type'] === 'has_and_belongs_to_many')) {
if (isset($this->relations[$relation])) {
if ($options['type'] === 'has_one') {
$this->checkRelation($relation, $this->{$relation}, $perm, $user_id);
} else {
// datafields gets special treatment...
if ($relation == 'datafields') {
foreach ($this->datafields as $entry) {
if ($entry->isNew() || $entry->isDirty()) {
if (!$perm->haveDfEntryPerm($entry->datafield_id, MvvPerm::PERM_WRITE)) {
throw new Exception(sprintf(
'Permission denied! The user is not '
. 'allowed to change value of field %s::datafields[%s] ("%s").', static::class, $entry->datafield_id, $entry->datafield->name));
}
}
}
} else {
foreach ($this->{$relation} as $r) {
$this->checkRelation($relation, $r, $perm, $user_id);
}
}
}
}
}
}
return true;
}
/**
* Checks the rights for a relation.
*
* @param string $relation_name Field name of relation.
* @param ModuleManagementModel $relation_object
* @param int $perm
* @param type $user_id
* @return boolean
* @throws Exception
*/
private function checkRelation($relation_name, $relation_object, $perm, $user_id)
{
if (($relation_object->isNew() || $relation_object->isDeleted())
&& !$perm->haveFieldPerm($relation_name, MvvPerm::PERM_CREATE, $user_id)) {
throw new Exception(sprintf(
'Permission denied! The user is not allowed to create/delete a relation %s::%s.',
get_class($relation_object), $relation_name));
} elseif ($relation_object->isDirty()) {
if ($relation_object instanceof ModuleManagementModel) {
$relation_object->verifyPermission($user_id);
} elseif (!$perm->haveFieldPerm($relation_name, MvvPerm::PERM_WRITE, $user_id)) {
throw new Exception(sprintf(
'Permission denied! The user is not allowed to modify a relation %s::%s.',
get_class($relation_object), $relation_name));
}
}
return true;
}
/**
* @see SimpleOrMap::store()
* Optional validation of values. Triggers logging of changes.
*
* @param boolean $validate True to validate values.
*/
public function store(/* $validate = true */)
{
$validate = true;
if (func_num_args() > 0) {
$validate = func_get_arg(0) !== false;
}
if ($validate) {
$this->validate();
}
if ($this->isNew() || $this->isDirty()) {
$this->editor_id = $GLOBALS['user']->id;
}
$stored = false;
if ($this->isNew()) {
$this->author_id = $GLOBALS['user']->id;
$stored = parent::store();
if ($stored) {
$this->logChanges('new');
}
} else {
$this->logChanges('update');
$stored = parent::store();
}
return $stored;
}
/**
* Validates the values before store. Throws an InvalidValuesException
* normally catched by form validation.
*
* @throws InvalidValuesException
*/
public function validate() {
}
/**
* @see SimpleOrMap::delete()
* Triggers logging.
*/
public function delete() {
$this->logChanges('delete');
return parent::delete();
}
/**
* Logs all changes of this object.
*
* @param type $action new, update or delete
* @return boolean Return true if logging was successful.
*/
protected function logChanges ($action = null) {
switch ($this->db_table) {
case 'abschluss' :
$logging = 'MVV_ABSCHLUSS';
$num_index = 1;
break;
case 'mvv_abschl_kategorie' :
$logging = 'MVV_KATEGORIE';
$num_index = 1;
break;
case 'mvv_abschl_zuord' :
$logging = 'MVV_ABS_ZUORD';
$num_index = 2;
break;
case 'mvv_dokument' :
$logging = 'MVV_DOKUMENT';
$num_index = 1;
break;
case 'mvv_dokument_zuord' :
$logging = 'MVV_DOK_ZUORD';
$num_index = 3;
break;
case 'fach' :
$logging = 'MVV_FACH';
$num_index = 1;
break;
case 'mvv_fachberater' :
$logging = 'MVV_FACHBERATER';
$num_index = 2;
break;
case 'mvv_fach_inst' :
$logging = 'MVV_FACHINST';
$num_index = 2;
break;
case 'mvv_lvgruppe' :
$logging = 'MVV_LVGRUPPE';
$num_index = 1;
break;
case 'mvv_lvgruppe_modulteil' :
$logging = 'MVV_LVMODULTEIL';
$num_index = 2;
break;
case 'mvv_lvgruppe_seminar' :
$logging = 'MVV_LVSEMINAR';
$num_index = 2;
break;
case 'mvv_modul' :
$logging = 'MVV_MODUL';
$num_index = 1;
break;
case 'mvv_modulteil' :
$logging = 'MVV_MODULTEIL';
$num_index = 1;
break;
case 'mvv_modulteil_deskriptor' :
$logging = 'MVV_MODULTEIL_DESK';
$num_index = 1;
break;
case 'mvv_modulteil_language' :
$logging = 'MVV_MODULTEIL_LANG';
$num_index = 2;
break;
case 'mvv_modulteil_stgteilabschnitt' :
$logging = 'MVV_MODULTEIL_STGTEILABS';
$num_index = 3;
break;
case 'mvv_modul_deskriptor' :
$logging = 'MVV_MODUL_DESK';
$num_index = 1;
break;
case 'mvv_modul_inst' :
$logging = 'MVV_MODULINST';
$num_index = 2;
break;
case 'mvv_modul_language' :
$logging = 'MVV_MODUL_LANG';
$num_index = 2;
break;
case 'mvv_modul_user' :
$logging = 'MVV_MODUL_USER';
$num_index = 3;
break;
case 'mvv_stgteil' :
$logging = 'MVV_STGTEIL';
$num_index = 1;
break;
case 'mvv_stgteilabschnitt' :
$logging = 'MVV_STGTEILABS';
$num_index = 1;
break;
case 'mvv_stgteilabschnitt_modul' :
$logging = 'MVV_STGTEILABS_MODUL';
$num_index = 2;
break;
case 'mvv_stgteilversion' :
$logging = 'MVV_STGTEILVERSION';
$num_index = 1;
break;
case 'mvv_stgteil_bez' :
$logging = 'MVV_STGTEILBEZ';
$num_index = 1;
break;
case 'mvv_stg_stgteil' :
$logging = 'MVV_STG_STGTEIL';
$num_index = 3;
break;
case 'mvv_studiengang' :
$logging = 'MVV_STUDIENGANG';
$num_index = 1;
break;
default:
return false;
}
if ($logging) {
$aff = null;
$coaff = null;
$debuginfo =null;
switch ($action) {
case 'new':
$logging .= '_NEW';
$debuginfo = $this->getDisplayName();
break;
case 'update':
$logging .= '_UPDATE';
break;
case 'delete':
$logging .= '_DEL';
$debuginfo = $this->getDisplayName();
break;
default:
return false;
}
$id_array = $this->getId();
switch ($num_index) {
case '1':
$aff = $id_array;
break;
case '2':
$aff = $id_array[0];
$coaff = $id_array[1];
break;
case '3':
$aff = $id_array[0];
$coaff = $id_array[1];
$debuginfo = $id_array[2];
break;
default:
return false;
}
if ($action == 'update') {
foreach ($this->content as $name => $value) {
if ($name == 'author_id' || $name == 'editor_id' || $name == 'mkdate' || $name == 'chdate' ) continue;
if ($this->isFieldDirty($name)) {
$info = ($num_index == 3) ? $debuginfo.';'.$value : $value;
StudipLog::log($logging, $aff, $coaff, $this->db_table.'.'.$name, $info, $editor_id);
}
}
} else {
StudipLog::log($logging, $aff, $coaff, $this->db_table, $debuginfo, $editor_id);
}
return true;
}
return false;
}
/**
* Sets a new id for this object.
*/
public function setNewId()
{
$this->setId($this->getNewId());
}
/**
* Enriches the model with data from other joined tables.
*
* @param string $query complete sql with all fields in select statement
* from main table
* @param array $params Array with the parameters used in query
* @param int $row_count Number of rows to return
* @param int $offset Offset where the result set starts
* @return SimpleOrMapCollection with all found objects or empty array
*/
public static function getEnrichedByQuery($query = null, $params = [],
$row_count = null, $offset = null)
{
$enriched = [];
$params = array_merge($params, self::$filter_params);
self::$filter_params = [];
if (!is_null($query)) {
if (!is_null($row_count)) {
$limit_sql = ' LIMIT ?';
$params[] = intval($row_count);
if (!is_null($offset)) {
$limit_sql .= ' OFFSET ?';
$params[] = intval($offset);
}
} else {
$limit_sql = '';
}
$stmt = DBManager::get()->prepare($query . $limit_sql);
$stmt->execute($params);
$model_object = new static();
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $data) {
$pkey = [];
foreach ($model_object->pk as $pk) {
$pkey[]= $data[$model_object->db_fields[$pk]['name']];
}
$data_object = clone $model_object;
foreach ($data as $key => $value) {
if (isset($data_object->db_fields[$key])) {
$data_object->setValue($key, $value);
} else {
$data_object->content[mb_strtolower($key)] = $value;
$data_object->content_db[mb_strtolower($key)] = $value;
}
}
$data_object->setId($pkey);
$data_object->setNew(false);
$enriched[join('', $pkey)] = $data_object;
}
}
return SimpleORMapCollection::createFromArray($enriched);
}
/**
* Returns the name of the object to display in a specific context. The
* default is the value from the db fields "name" or "bezeichnung" or an
* empty string if no such fields exist. This method is overwritten by most
* of the mvv objects to display more complex names glued together from
* fields of related objects.
*
* @param mixed $options An optional parameter to set display options.
* @return string The name for
*/
public function getDisplayName($options = self::DISPLAY_DEFAULT)
{
if ($this->isField('name')) {
return (string) $this->getValue('name');
}
if ($this->isField('bezeichnung')) {
return (string) $this->getValue('bezeichnung');
}
return '';
}
/**
* Returns the display name of this class.
*
* @return string the display name of this class
*/
public static function getClassDisplayName($long = false)
{
return 'Module Management Model';
}
/**
* Creates a sql clause to set constraints in a query by given filters.
*
* The values of the given filters can be either a scalar value or an array.
*
* If the value of the given filter is '__undefined__' then it matches
* against NULL or an empty string.
*
* If the column name has
* a comparison operator at its end (delimited by a blank), this operator
* is used.
*
* To filter for semesters the column name has to be 'start_sem.beginn' for
* the start semester and 'end_sem.ende' for the end semester according to
* the joins with the semester_data table and table aliases in the sql
* statement.
*
* @param array $filter An associative array with filters where the key is
* the column name to filter against the given value.
* @param bool $where if true returns a complete where statement
* @param string SQL-where part glued with an "OR" at the end of the
* filter sql part.
* @return string The sql clause
*/
public static function getFilterSql($filter, $where = false, $or_sql = null)
{
$sql_parts = [];
foreach ((array) $filter as $col => $val) {
$col = trim($col);
if (is_array($val)) {
if (sizeof($val)) {
$sql_parts[] = $col . ' IN('
. join(',', array_map(
function ($val) {
return DBManager::get()->quote($val);
}, $val))
. ') ';
}
} else if (trim($val)) {
if ($val == '__undefined__') {
$sql_parts[] = '(ISNULL(' . $col . ') OR ' . $col . " = '')";
} else {
if (preg_match('/([\w\.]+)\s+([\<\>\!]\=?)/', $col, $matches)) {
$sql_parts[] = trim($matches[1]) . ' ' . $matches[2] . ' '
. DBManager::get()->quote($val) . ' ';
} else if ($col == 'start_sem.beginn') {
if ((int) $val >= 0) {
// start semester filter for Module, Studiengaenge, ...
$sql_parts[] = '(start_sem.beginn <= '
. DBManager::get()->quote($val)
. ' OR ISNULL(start_sem.beginn))';
}
} else if ($col == 'end_sem.ende') {
if ((int) $val >= 0) {
// end semester filter for Module, Studiengaenge, ...
$sql_parts[] = '(end_sem.ende >= '
. DBManager::get()->quote($val)
. ' OR ISNULL(end_sem.ende))';
}
} else {
$sql_parts[] = $col . ' = '
. DBManager::get()->quote($val) . ' ';
}
}
}
}
$sql = implode(' AND ', $sql_parts);
if (mb_strlen($sql)) {
if ($or_sql) {
$sql = '(' . $sql . ') OR (' . $or_sql . ')';
}
$sql = $where ? ' WHERE (' . $sql . ') ' : ' AND (' . $sql . ') ';
}
return $sql;
}
/**
* Verifies a field name or an array of field names if they are permitted for
* sorting the result. If ok returns the given sort fields. If not, returns
* the given standard_field or null.
*
* @param string|array $sort the fields to check
* @param string $additional_fields additional allowed fields
* @return string|null the verified sort fields
*/
protected static function checkSortFields($sort, $standard_field = null,
$additional_fields = [])
{
if (!is_array($sort)) {
$sort = explode(',', $sort);
}
$sorm = new static();
if (sizeof(array_intersect(
array_merge(array_keys($sorm->db_fields), $additional_fields),
$sort))) {
return implode(',', $sort);
}
return $standard_field;
}
/**
* Checks for valid fields and creates a sort statement for queries.
*
* @param string|array $sort The field(s) to sort by.
* @param string $order The direction (ASC|DESC)
* @param string $standard_field
* @param array $additional_fields Calculated columns.
* @return string The sort query part.
*/
protected static function createSortStatement($sort, $order = 'ASC',
$standard_field = null, $additional_fields = [])
{
$order = mb_strtoupper(trim($order)) !== 'DESC' ? ' ASC' : ' DESC';
if (!is_array($sort)) {
$sort = explode(',', $sort);
}
$sort = array_map('trim', $sort);
$sorm_name = static::class;
$sorm = new $sorm_name();
$allowed_fields = array_intersect(
$sort,
array_merge(
array_keys($sorm->db_fields),
$additional_fields
)
);
if (count($allowed_fields) > 0) {
return implode("{$order},", $allowed_fields) . $order;
}
return $standard_field;
}
/**
* Returns an SimpleOrMap object as an Array.
* Its like a static version of SimpleOrMap::toArray but
* returns all content fields. Usefull as callback function.
*
* @param SimpleORMap $sorm The SimpleOrMap object to transform
* @param bool $to_utf8 If true (default), the data will be utf8 transformed.
* @return array The array with all content fields from object.
*/
public static function getContentArray(SimpleORMap $sorm, $to_utf8 = true)
{
return $sorm->contentToArray($to_utf8);
}
/**
* Returns the number of objects of this type. Optionally reduced by
* filter criteria.
*
* @param array An array with filter criteria.
* See ApplicationSimpleORMap::getFilter().
* @return int The number of rows.
*/
public static function getCount($filter = null)
{
if ($filter) {
$filter_sql = self::getFilterSql($filter, true);
} else {
$filter_sql = '';
}
$sorm = new static();
$db = DBManager::get()->query('SELECT COUNT(*) FROM '
. $sorm->db_table . $filter_sql);
return $db->fetchColumn(0);
}
/**
* Returns the number of rows found by the given sql and filters.
*
* @param string $sql The sql query part.
* @param array $filter An array of filters with respect to the query part.
* @return int The number of rows.
*/
public static function getCountBySql($sql, $filter = null)
{
$stmt = DBManager::get()->prepare($sql . self::getFilterSql($filter,
true));
$stmt->execute(self::$filter_params);
return $stmt->fetchColumn(0);
}
/**
* Sets the language for localized fields and the locale environment
* globally.
* Possible values are configured in mvv_config.php.
*
* @see mvv_config.php
* @param string $language The language.
*/
public static final function setLanguage($language)
{
$language = mb_strtoupper(mb_strstr($language . '_', '_', true));
if (isset($GLOBALS['MVV_LANGUAGES']['values'][$language])) {
$locale = $GLOBALS['MVV_LANGUAGES']['values'][$language]['locale'];
setLocaleEnv($locale);
self::setContentLanguage($language);
// load config file again
require $GLOBALS['STUDIP_BASE_PATH'] . '/config/mvv_config.php';
}
}
/**
* Switches the content to the given language.
* Compared to ModuleManagementModel::setLanguage() strings translated with
* gettext are always in the prefered language selected by the user.
*
* @param string $language The language code (see mvv_config.php)
*/
public static function setContentLanguage($language)
{
if (!is_array($GLOBALS['MVV_LANGUAGES']['values'][$language])) {
throw new InvalidArgumentException();
}
$locale = $GLOBALS['MVV_LANGUAGES']['values'][$language]['locale'];
I18NString::setContentLanguage($locale);
self::$language = $language;
}
public function getAvailableTranslations()
{
$translations[] = $GLOBALS['MVV_LANGUAGES']['default'];
$stmt = DBManager::get()->prepare('SELECT DISTINCT `lang` '
. 'FROM i18n '
. 'WHERE `object_id` = ? AND `table` = ?');
$stmt->execute([$this->id, $this->db_table]);
foreach ($stmt->fetchAll() as $locale) {
$language = mb_strtoupper(mb_strstr($locale['lang'], '_', true));
if (is_array($GLOBALS['MVV_LANGUAGES']['values'][$language])) {
$translations[] = $language;
}
}
return $translations;
}
/**
* Returns the currently selected language.
*
* @return string The currently selected language.
*/
public static final function getLanguage()
{
$language = self::$language ?: $GLOBALS['MVV_LANGUAGES']['default'];
return $language;
}
/**
* Returns the suffix for ordinal numbers if the selected locale is EN or
* a simple point if not.
*
* @param type $num
* @return string The ordinal suffix or a point.
*/
public static function getLocaleOrdinalNumberSuffix($num)
{
if (ModuleManagementModel::getLanguage() == 'EN') {
if ($num % 100 < 11 || $num % 100 > 13) {
switch ($num % 10) {
case 1: return 'st';
case 2: return 'nd';
case 3: return 'rd';
}
}
return 'th';
}
return '.';
}
/**
* Returns an array of all values for given class with status "public"
* defined by configuration.
*
* @return array Array of defined values for status "public".
*/
public static function getPublicStatus($class_name = null)
{
$class_name = $class_name ?: static::class;
$class_name = 'MVV_' . mb_strtoupper($class_name);
$public_status = [];
if (is_array($GLOBALS[$class_name]['STATUS']['values'])) {
foreach ($GLOBALS[$class_name]['STATUS']['values'] as $key => $status) {
if ($status['public']) {
$public_status[] = $key;
}
}
}
return $public_status;
}
/**
* Returns the status of this object.
* Some MVV objects have a status declared in mvv_config.php.
*
* @return string|null The status or null if the object has no status.
*/
public function getStatus()
{
if ($this->isField('stat')) {
return $this->stat;
}
return null;
}
/**
* Returns whether this object has a public status. Public status means that
* this object is public visible. The possible status are defined
* in mvv_config.php. The set of possible status can be restricted by an
* optional filter. Only the statis given in filter are checkrd.
*
* @param array $filter An array of status keys.
* @return boolean True if object has an public status.
*/
public function hasPublicStatus($filter = null)
{
$public_status = ModuleManagementModel::getPublicStatus(static::class);
$filtered_status = $filter
? array_intersect(words($filter), $public_status)
: $public_status;
$status = $this->getStatus();
return $status ? in_array($status, $filtered_status) : false;
}
public function getResponsibleInstitutes()
{
return [];
}
/**
* Returns a string that identify a variant of this object. Returns an empty
* string if no variant exists for this object.
*
* @return string String to identify a variant.
*/
public function getVariant()
{
return '';
}
/**
* Locates and returns an item from cache
*
* @param string $id Id of the item
* @param string $index Optional index for the cache, defaults to class name
* @return ModuleManagementModel object or null
*/
public static function findCached($id, $index = null)
{
// Prevent warnings
if ($id === null || $id === false) {
return null;
}
return self::fromCache($index ?? static::class, $id, function () use ($id) {
return static::find($id);
});
}
/**
* Clears the cache for a given index or completely. If this method is
* called on this abstract class, it will always clear the whole cache.
* Otherwise it will clear the cache for the subclass of this class or
* the given index.
*
* @param mixed $index Optional index to clear
*/
public static function clearCache($index = null)
{
if (static::class === self::class && $index === null) {
static::$object_cache = [];
} else {
$index = $index ?? static::class;
unset(static::$object_cache[$index]);
}
}
/**
* Loads an item from cache and retrieves and stores it if it is not
* present.
*
* @param string $index Index for the cache
* @param string $id Id of the item
* @param Callable $finder Function to actually locate the item.
* @return ModuleManagementModel object or null
*/
protected static function fromCache($index, $id, Callable $finder)
{
// Leave immeditately if $id cannot be used as array index
if (!is_string($id) && !is_int($id)) {
return $finder();
}
if (!isset(static::$object_cache[$index])) {
static::$object_cache[$index] = [];
}
if (!array_key_exists($id, static::$object_cache[$index])) {
static::$object_cache[$index][$id] = $finder();
}
return static::$object_cache[$index][$id];
}
}